Implementation Guide: The http-server Library
We've provided you with http-server.c and http-server.h. This is code for
you to treat as a library, you shouldn't change it at all (though you may
learn from reading it!).
It provides one function for you to use:
void start_server(void(*handler)(char*, int), int port);
This function takes two arguments: a handler that takes HTTP requests and
writes responses, and a port for telling the server what address to use. The
port can be 0 to ask the operating system to choose one for us.
The type for handler is the type of a function pointer, which is the address
of a function. To use start_server, we define a function (it can have any
name) with the appropriate signature and pass it into start_server as an
argument. The handler function takes two arguments:
void(*handler)(char*, int)
- The first, the
char*, is a string containing the request from the internet. It will be an HTTP request. - The second, the
int, is a file descriptor that our program can use to write a response using thewritesystem call
Here's a straightforward example that just responds to the user with a constant string:
#include "http-server.h" // http-server includes a lot of other useful things
char const* HTTP_200_OK = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n";
void handle_response(char *request, int client_socket) {
printf("The user sent a request with: %s\n", request);
write(client_socket, HTTP_200_OK, strlen(HTTP_200_OK));
write(client_socket, "Hello!", 6);
}
int main() {
start_server(&handle_response, 0);
}
$ gcc -o print-request print-request.c http-server.c
$ ./print-request
Server started on port PPPPP (will be a specific number for you)
# the lines below print *when the request is received*
The user sent a request with: GET /post?user=joe&message=hi HTTP/1.1
Host: localhost:36611
User-Agent: curl/7.68.0
Accept: */*
The user sent a request with: GET /post?user=aaron&message=sup HTTP/1.1
Host: localhost:36611
User-Agent: curl/7.68.0
Accept: */*
(in another terminal)
$ curl "http://localhost:PPPPP/post?user=joe&message=hi"
Hello!
$ curl "http://localhost:PPPPP/post?user=aaron&message=sup"
Hello!
Then, every time the server gets a request, it will call the handle_request
function, which will print the contents of the request, and then use the write system call to send a response back to the client (in this case with a constant string). This is the entry point to our program, and it's a good starting point for you to build up from!
One other tip for using start_server:
- If
portis 0, the library will pick an open port provided by the operating system. This is convenient for getting started - However, when you restart a program it's annoying to have to change the port number in any requests you have in your bash history, etc. So you can also provide the port number as an argument, which could be hardcoded or taken from a command-line argument. You can do that to re-run the program with the same port it used last time to keep your examples consistent
HTTP Requests
The first argument to handler is a char* with a a reference to the HTTP
request. For our chat-server, all requests will be GET requests and will
look something like
GET /post?user=joe&message=hi HTTP/1.1
Host: localhost:36611 User-Agent:
curl/7.68.0 Accept: */ *
The string after GET is the path of the request, and that's the part that will
change depending on the type of request we get. Part of the job of the
chat-server will be to extract the relevant information from the path, like
which action to take (post, react, etc.) and the parameters of the action (user,
message, etc.) in order to work with them.
It probably makes sense to use some C string library functions to do this. In
particular strtok will allow you to “split up” a string by replacing
delimiters like , &, and = with null terminators. You may also find
strcspn, strstr, or strpbrk useful; it's up to you to come up with a plan
to put these together to extract the information out of each request.
HTTP Responses
The second argument to handler is an int that is a file descriptor for the
response. The http-server library has set things up so writing to the socket
sends bytes directly back to whatever client made the request.
The format of a HTTP response for a successful response is
HTTP/1.1 200 OK
Content-Type: text/plain
... response body ...
Note: A nuance of this is that the newlines in HTTP responses are not just
\n; they are required to be
\r\n (which, interestingly,
is how line endings
work on Windows). Some software will work with just a \n for line breaks, but
the correct thing to do is use \r\n, so in a string literal the HTTP header above
would look like
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n...response body...
There are many other Content-Types in the world other than text/plain! But
we will focus on just plain text reponses for this assignment.