CS110: Principles of Computer Systems
Winter 2021-2022
Stanford University
Instructors: Nick Troccoli and Jerry Cain
Introduction to Networking
Servers / HTTP
Clients, Servers and APIs
Networking System Calls
assign6: implement an HTTP Proxy that sits between a client device and a web server to monitor, block or modify web traffic.
There's much more to networking than we have time to cover. We are focusing on the core ideas at the application level. Take CS144 if you're interested in learning more!
(diagram from cs144 slides)
We're writing short client/server programs and focusing on the core aspects of how networked programs function. Take CS142 if you're interested in learning more about writing servers and web-based programs!
// Opens a connection to a server (returns kClientSocketError on error)
int createClientSocket(const string& host, unsigned short port);
New CS110 helper function to connect to a server:
(Later on, we will learn how to implement createClientSocket!)
I am running a server on myth64.stanford.edu, port 12345 that can tell you the current time. Whenever a client connects to it, the server sends back the time as text. This client program connects to that server and prints the response using sockbuf/iosockstream.
int main(int argc, char *argv[]) {
// Open a connection to the server
int socketDescriptor = createClientSocket("myth64.stanford.edu", 12345);
// Read in the data from the server (sockbuf descructor closes descriptor)
sockbuf socketBuffer(socketDescriptor);
iosockstream socketStream(&socketBuffer);
string timeline;
getline(socketStream, timeline);
// Print the data from the server
cout << timeline << endl;
return 0;
}
myth$ ./time-client
Fri Feb 25 08:15:22 2022
myth$
sockbuf/iosockstream let us avoid calling read/write directly, which is more cumbersome and is C-level instead of C++-level:
int main(int argc, char *argv[]) {
// Open a connection to the server
int socketDescriptor = createClientSocket("myth64.stanford.edu", 12345);
// Read in the data from the server (assumed to be at most 1024 byte string)
char buf[1024];
size_t bytes_read = 0;
while (true) {
size_t read_this_time = read(socketDescriptor, buf + bytes_read, sizeof(buf) - bytes_read);
if (read_this_time == 0) break;
bytes_read += read_this_time;
}
buf[bytes_read] = '\0';
close(socketDescriptor);
// print the data from the server
cout << buf << flush;
return 0;
}
// Creates a socket to listen for incoming requests (returns kServerSocketFailure on error)
int createServerSocket(unsigned short port, int backlog = kDefaultBacklog);
New CS110 helper function to create a socket descriptor to listen for incoming connections:
(Later on, we will learn how to implement createServerSocket!)
// Waits for an incoming connection and returns a descriptor for that connection
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
This code runs a server on port 12345 that can tell you the current time. Whenever a client connects to it, the server sends back the time as text.
int main(int argc, char *argv[]) {
// Create a server socket we can use to listen for incoming requests
int serverSocket = createServerSocket(12345);
while (true) {
// Wait for an incoming client connection and establish a descriptor for it
int clientDescriptor = accept(serverSocket, NULL, NULL);
// Make a string of the current date and time and send it to the client
string dateTime = getCurrentDateTime();
sockbuf socketBuffer(clientDescriptor); // destructor closes socket
iosockstream socketStream(&socketBuffer);
socketStream << dateTime << endl;
}
close(serverSocket);
return 0;
}
// This function returns a string representation of the current date and time.
static string getCurrentDateTime() {
time_t rawtime;
time(&rawtime);
struct tm tm;
gmtime_r(&rawtime, &tm);
char timestr[128]; // more than big enough
/* size_t len = */ strftime(timestr, sizeof(timestr), "%c", &tm);
return timestr;
}
The function that actually creates the time string is not important - it could be any kind of data. The key takeaway is how the server listens for and responds to client connections.
Problem: servers may have many incoming connections at once. We need to be able to handle connections concurrently!
int main(int argc, char *argv[]) {
// Create a server socket we can use to listen for incoming requests
int serverSocket = createServerSocket(12345);
ThreadPool pool(kNumThreads);
while (true) {
// Wait for an incoming client connection and establish a descriptor for it
int clientDescriptor = accept(serverSocket, NULL, NULL);
// Add a task to make a string of the current date and time and send it to the client
pool.schedule([clientDescriptor]() {
string dateTime = getCurrentDateTime();
sockbuf socketBuffer(clientDescriptor); // destructor closes socket
iosockstream socketStream(&socketBuffer);
socketStream << dateTime << endl;
});
}
close(serverSocket);
return 0;
}
This server adds tasks to a thread pool to concurrently respond to client connections.
Our time server chose to send a raw single-line string response to a client. A client connecting must be aware of this to know how to handle / use the response data.
Key idea: a client and server must agree on the format of the data being sent back and forth so they know what to send and how to parse the response.
HTTP ("HyperText Transfer Protocol") is the predominant protocol for Internet requests and responses (e.g. webpages, web resources, web APIs).
What happens when you type a URL into your web browser?
Note: a more secure version of HTTP, called HTTPS, is predominant today and encrypts requests/responses. Most conversations happen over HTTPS, but we're focusing just on HTTP.
GET / HTTP/1.0 Host: www.google.com ... [BLANK LINE]
The first line is the request line. It specifies general information about the kind of request and the protocol version. 3 components:
- request type ("verb" or "method")
- request path
- request protocol version
GET / HTTP/1.0
"verb" or "method": what kind of request are we making?
- I wish to fetch a resource (GET)
- I wish to upload some new data (POST / PUT)
- I wish to get a preview of information about a resource (HEAD)
GET / HTTP/1.0
"path": what server resource am I referring to?
- just the component after the host name
GET / HTTP/1.0
"HTTP protocol version": what version of HTTP am I speaking?
GET / HTTP/1.0 Host: www.google.com ... [BLANK LINE]
The second and onwards lines are each a *header* included to provide more information. They are key-value pairs.
Examples:
- Host ("what host am I sending this to?")
- Content-Type ("what type of content am I uploading?")
- User-Agent ("what kind of user program sent this request?")
- Cookie ("what cookie does the user have for speaking to this server?")
GET / HTTP/1.0 Host: www.google.com ... [BLANK LINE]
The request ends with a blank line.
GET / HTTP/1.0 Host: www.google.com ... [BLANK LINE] {request body}
Some requests (like POST) that are uploading data have a request *body* after this blank line.
HTTP/1.0 200 OK Content-Type: text/html [BLANK LINE] {response body}
The first line is the status line. It specifies general information about how the request was handled and the protocol version. 2 components:
- response protocol version
- response status code
HTTP/1.0 200 OK
"HTTP protocol version": what version of HTTP am I speaking?
HTTP/1.0 200 OK
How did things go? Examples:
- A-ok! (20X)
- What you're looking for is somewhere else (30X)
- you did something wrong (40X)
- we did something wrong (50X)
HTTP/1.0 200 OK
Some humorous status codes:
- 418 (I'm a teapot) - "Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout."
- 451 (unavailable for legal reasons)
HTTP/1.0 200 OK Content-Type: text/html [BLANK LINE] {response body}
The second and onwards lines are each a *header* included to provide more information. They are key-value pairs.
Examples:
- Content-Type ("what type of content am I including?")
- Content-Length ("how much content am I including?")
- Set-Cookie ("here's a cookie you should remember for future requests")
HTTP/1.0 200 OK Content-Type: text/html [BLANK LINE] {response body}
Following a blank line, there is the response body ("payload") containing data the server sent back. E.g. HTML to display.
Next time: More HTTP and servers