diff --git a/README.md b/README.md index 8fd528143..61ee1122f 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,24 @@ In this project, we'll finish the implementation of a web server in C. What you need to write: -* HTTP request parser -* HTTP response builder -* LRU cache - * Doubly linked list (some functionality provided) - * Use existing hashtable functionality (below) +- HTTP request parser +- HTTP response builder +- LRU cache -* Your code will interface with the existing code. Understanding the existing + - Doubly linked list (some functionality provided) + - Use existing hashtable functionality (below) + +- Your code will interface with the existing code. Understanding the existing code is an expected part of this challenge. What's already here: -* `net.h` and `net.c` contain low-level networking code -* `mime.h` and `mime.c` contains functionality for determining the MIME type of a file -* `file.h` and `file.c` contains handy file-reading code that you may want to utilize, namely the `file_load()` and `file_free()` functions for reading file data and deallocating file data, respectively (or you could just perform these operations manually as well) -* `hashtable.h` and `hashtable.c` contain an implementation of a hashtable (this one is a bit more complicated than what you built in the Hashtables sprint) -* `llist.h` and `llist.c` contain an implementation of a doubly-linked list (used solely by the hashable--you don't need it) -* `cache.h` and `cache.c` are where you will implement the LRU cache functionality for days 3 and 4 +- `net.h` and `net.c` contain low-level networking code +- `mime.h` and `mime.c` contains functionality for determining the MIME type of a file +- `file.h` and `file.c` contains handy file-reading code that you may want to utilize, namely the `file_load()` and `file_free()` functions for reading file data and deallocating file data, respectively (or you could just perform these operations manually as well) +- `hashtable.h` and `hashtable.c` contain an implementation of a hashtable (this one is a bit more complicated than what you built in the Hashtables sprint) +- `llist.h` and `llist.c` contain an implementation of a doubly-linked list (used solely by the hashable--you don't need it) +- `cache.h` and `cache.c` are where you will implement the LRU cache functionality for days 3 and 4 ## What is a Web Server? @@ -29,17 +30,17 @@ requests for HTML pages), and returns responses (e.g. HTML pages). Other common ## Reading -* [Networking Background](guides/net.md) -* [Doubly-Linked Lists](guides/dllist.md) -* [LRU Caches](guides/lrucache.md) -* [MIME types](guides/mime.md) +- [Networking Background](guides/net.md) +- [Doubly-Linked Lists](guides/dllist.md) +- [LRU Caches](guides/lrucache.md) +- [MIME types](guides/mime.md) ## Assignment We will write a simple web server that returns files and some specialized data on a certain endpoint. -* `http://localhost:3490/d20` should return a random number between 1 and 20 inclusive as `text/plain` data. -* Any other URL should map to the `serverroot` directory and files that lie within. For example: +- `http://localhost:3490/d20` should return a random number between 1 and 20 inclusive as `text/plain` data. +- Any other URL should map to the `serverroot` directory and files that lie within. For example: ``` http://localhost:3490/index.html @@ -72,7 +73,7 @@ _Read through all the main and stretch goals before writing any code to get an o 1. Implement `send_response()`. This function is responsible for formatting all the pieces that make up an HTTP response into the proper format that clients expect. In other words, it needs to build a complete HTTP response with the given parameters. It should write the response to the string in the `response` variable. - + The total length of the header **and** body should be stored in the `response_length` variable so that the `send()` call knows how many bytes to send out over the wire. @@ -86,7 +87,7 @@ _Read through all the main and stretch goals before writing any code to get an o > the header. But the `response_length` variable used by `send()` is the > total length of both header and body. - You can test whether you've gotten `send_response` working by calling the `resp_404` function from somewhere inside the `main` function, and seeing if the client receives the 404 response. + You can test whether you've gotten `send_response` working by calling the `resp_404` function from somewhere inside the `main` function, and seeing if the client receives the 404 response. 2. Examine `handle_http_request()` in the file `server.c`. @@ -153,24 +154,24 @@ The hashtable code is already written and can be found in `hashtable.c`. Algorithm: - * Allocate a new cache entry with the passed parameters. - * Insert the entry at the head of the doubly-linked list. - * Store the entry in the hashtable as well, indexed by the entry's `path`. - * Increment the current size of the cache. - * If the cache size is greater than the max size: - * Remove the cache entry at the tail of the linked list. - * Remove that same entry from the hashtable, using the entry's `path` and the `hashtable_delete` function. - * Free the cache entry. - * Ensure the size counter for the number of entries in the cache is correct. + - Allocate a new cache entry with the passed parameters. + - Insert the entry at the head of the doubly-linked list. + - Store the entry in the hashtable as well, indexed by the entry's `path`. + - Increment the current size of the cache. + - If the cache size is greater than the max size: + - Remove the cache entry at the tail of the linked list. + - Remove that same entry from the hashtable, using the entry's `path` and the `hashtable_delete` function. + - Free the cache entry. + - Ensure the size counter for the number of entries in the cache is correct. 2. Implement `cache_get()` in `cache.c`. Algorithm: - * Attempt to find the cache entry pointer by `path` in the hash table. - * If not found, return `NULL`. - * Move the cache entry to the head of the doubly-linked list. - * Return the cache entry pointer. + - Attempt to find the cache entry pointer by `path` in the hash table. + - If not found, return `NULL`. + - Move the cache entry to the head of the doubly-linked list. + - Return the cache entry pointer. 3. Add caching functionality to `server.c`. @@ -181,11 +182,11 @@ The hashtable code is already written and can be found in `hashtable.c`. If it's not there: - * Load the file from disk (see `file.c`) - * Store it in the cache - * Serve it + - Load the file from disk (see `file.c`) + - Store it in the cache + - Serve it -There's a set of unit tests included to ensure that your cache implementation is functioning correctly. From the `src` directory, run `make tests` in order to run the unit tests against your implementation. +There's a set of unit tests included to ensure that your cache implementation is functioning correctly. From the `src` directory, run `make tests` in order to run the unit tests against your implementation. ### Stretch Goals @@ -247,4 +248,3 @@ When a new connection comes in, launch a thread to handle it. Be sure to lock the cache when a thread accesses it so the threads don't step on each other's toes and corrupt the cache. Also have thread cleanup handlers to handle threads that have died. - diff --git a/src/cache.c b/src/cache.c index c72975cdd..00e0b86e1 100644 --- a/src/cache.c +++ b/src/cache.c @@ -9,9 +9,23 @@ */ struct cache_entry *alloc_entry(char *path, char *content_type, void *content, int content_length) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + struct cache_entry *ce = malloc(sizeof *ce); + + if (ce == NULL) + { + return NULL; + } + + ce->path = strdup(path); + ce->content_type = strdup(content_type); + ce->content_length = content_length; + + ce->content = malloc(content_length); + memcpy(ce->content, content, content_length); + + ce->prev = ce->next = NULL; + + return ce; } /** @@ -19,9 +33,10 @@ struct cache_entry *alloc_entry(char *path, char *content_type, void *content, i */ void free_entry(struct cache_entry *entry) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + free(entry->path); + free(entry->content_type); + free(entry->content); + free(entry); } /** @@ -30,10 +45,13 @@ void free_entry(struct cache_entry *entry) void dllist_insert_head(struct cache *cache, struct cache_entry *ce) { // Insert at the head of the list - if (cache->head == NULL) { + if (cache->head == NULL) + { cache->head = cache->tail = ce; ce->prev = ce->next = NULL; - } else { + } + else + { cache->head->prev = ce; ce->next = cache->head; ce->prev = NULL; @@ -46,13 +64,16 @@ void dllist_insert_head(struct cache *cache, struct cache_entry *ce) */ void dllist_move_to_head(struct cache *cache, struct cache_entry *ce) { - if (ce != cache->head) { - if (ce == cache->tail) { + if (ce != cache->head) + { + if (ce == cache->tail) + { // We're the tail cache->tail = ce->prev; cache->tail->next = NULL; - - } else { + } + else + { // We're neither the head nor the tail ce->prev->next = ce->next; ce->next->prev = ce->prev; @@ -65,7 +86,6 @@ void dllist_move_to_head(struct cache *cache, struct cache_entry *ce) } } - /** * Removes the tail from the list and returns it * @@ -91,9 +111,19 @@ struct cache_entry *dllist_remove_tail(struct cache *cache) */ struct cache *cache_create(int max_size, int hashsize) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + struct cache *cache = malloc(sizeof *cache); + + if (cache == NULL) + { + return NULL; + } + + cache->index = hashtable_create(hashsize, NULL); + cache->head = cache->tail = NULL; + cache->max_size = max_size; + cache->cur_size = 0; + + return cache; } void cache_free(struct cache *cache) @@ -102,7 +132,8 @@ void cache_free(struct cache *cache) hashtable_destroy(cache->index); - while (cur_entry != NULL) { + while (cur_entry != NULL) + { struct cache_entry *next_entry = cur_entry->next; free_entry(cur_entry); @@ -122,9 +153,26 @@ void cache_free(struct cache *cache) */ void cache_put(struct cache *cache, char *path, char *content_type, void *content, int content_length) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // Allocate a new cache entry with the passed parameters. + struct cache_entry *cache_entry = alloc_entry(path, content_type, content, content_length); + // Insert the entry at the head of the doubly-linked list. + dllist_insert_head(cache, cache_entry); + // Store the entry in the hashtable as well, indexed by the entry's path. + hashtable_put(cache->index, path, cache_entry); + // Increment the current size of the cache. + cache->cur_size++; + // If the cache size is greater than the max size: + if (cache->cur_size > cache->max_size) + { + // Remove the cache entry at the tail of the linked list. + struct cache_entry *oldtail = dllist_remove_tail(cache); + // Remove that same entry from the hashtable, using the entry's path and the hashtable_delete function. + hashtable_delete(cache->index, oldtail->path); + // Free the cache entry. + free_entry(oldtail); + // Ensure the size counter for the number of entries in the cache is correct. + printf("CUrrent size: %d, should be: %d\n", cache->cur_size, cache->max_size - 1); + } } /** @@ -132,7 +180,15 @@ void cache_put(struct cache *cache, char *path, char *content_type, void *conten */ struct cache_entry *cache_get(struct cache *cache, char *path) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // Attempt to find the cache entry pointer by path in the hash table. + struct cache_entry *ce = hashtable_get(cache->index, path); + // If not found, return NULL. + if (ce == NULL) + { + return NULL; + } + // Move the cache entry to the head of the doubly-linked list. + dllist_move_to_head(cache, ce); + // Return the cache entry pointer. + return ce; } diff --git a/src/mime.c b/src/mime.c index 4013b5ca8..cd43bbc87 100644 --- a/src/mime.c +++ b/src/mime.c @@ -9,7 +9,8 @@ */ char *strlower(char *s) { - for (char *p = s; *p != '\0'; p++) { + for (char *p = s; *p != '\0'; p++) + { *p = tolower(*p); } @@ -22,25 +23,49 @@ char *strlower(char *s) char *mime_type_get(char *filename) { char *ext = strrchr(filename, '.'); - - if (ext == NULL) { + if (ext == NULL) + { return DEFAULT_MIME_TYPE; } - + ext++; strlower(ext); // TODO: this is O(n) and it should be O(1) - if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0) { return "text/html"; } - if (strcmp(ext, "jpeg") == 0 || strcmp(ext, "jpg") == 0) { return "image/jpg"; } - if (strcmp(ext, "css") == 0) { return "text/css"; } - if (strcmp(ext, "js") == 0) { return "application/javascript"; } - if (strcmp(ext, "json") == 0) { return "application/json"; } - if (strcmp(ext, "txt") == 0) { return "text/plain"; } - if (strcmp(ext, "gif") == 0) { return "image/gif"; } - if (strcmp(ext, "png") == 0) { return "image/png"; } + if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0) + { + return "text/html"; + } + if (strcmp(ext, "jpeg") == 0 || strcmp(ext, "jpg") == 0) + { + return "image/jpg"; + } + if (strcmp(ext, "css") == 0) + { + return "text/css"; + } + if (strcmp(ext, "js") == 0) + { + return "application/javascript"; + } + if (strcmp(ext, "json") == 0) + { + return "application/json"; + } + if (strcmp(ext, "txt") == 0) + { + return "text/plain"; + } + if (strcmp(ext, "gif") == 0) + { + return "image/gif"; + } + if (strcmp(ext, "png") == 0) + { + return "image/png"; + } return DEFAULT_MIME_TYPE; } \ No newline at end of file diff --git a/src/server.c b/src/server.c index ea43306fc..305487f84 100644 --- a/src/server.c +++ b/src/server.c @@ -34,7 +34,7 @@ #include "mime.h" #include "cache.h" -#define PORT "3490" // the port users will be connecting to +#define PORT "3490" // the port users will be connecting to #define SERVER_FILES "./serverfiles" #define SERVER_ROOT "./serverroot" @@ -53,39 +53,58 @@ int send_response(int fd, char *header, char *content_type, void *body, int cont const int max_response_size = 262144; char response[max_response_size]; + time_t rawtime; + struct tm *timestamp; + char buffer[100]; + + time(&rawtime); + + timestamp = localtime(&rawtime); + strftime(buffer, 100, "%a %b %d %T %Z %Y", timestamp); // Build HTTP response and store it in response - /////////////////// - // IMPLEMENT ME! // - /////////////////// + int response_length = snprintf( + response, max_response_size, + "%s\n" + "Date: %s\n" + "Connection: close\n" + "Content-Length: %d\n" + "Content-Type: %s\n" + "\n", + header, buffer, content_length, content_type); // Send it all! + // To get binary working memcopy or two sends (one response, two body) + // Talked about in Tuesday lecture int rv = send(fd, response, response_length, 0); - if (rv < 0) { + if (rv < 0) + { + perror("send"); + } + + rv = send(fd, body, content_length, 0); + + if (rv < 0) + { perror("send"); } return rv; } - /** * Send a /d20 endpoint response */ void get_d20(int fd) { + char str[5]; // Generate a random number between 1 and 20 inclusive - - /////////////////// - // IMPLEMENT ME! // - /////////////////// - + int num = (rand() % (20 - 1 + 1) + 1); + // Conver number to string + sprintf(str, "%d", num); // Use send_response() to send it back as text/plain data - - /////////////////// - // IMPLEMENT ME! // - /////////////////// + send_response(fd, "HTTP/1.1 200 OK", "text/plain", str, strlen(str)); } /** @@ -94,21 +113,21 @@ void get_d20(int fd) void resp_404(int fd) { char filepath[4096]; - struct file_data *filedata; + struct file_data *filedata; char *mime_type; // Fetch the 404.html file snprintf(filepath, sizeof filepath, "%s/404.html", SERVER_FILES); filedata = file_load(filepath); - if (filedata == NULL) { + if (filedata == NULL) + { // TODO: make this non-fatal fprintf(stderr, "cannot find system 404 file\n"); exit(3); } mime_type = mime_type_get(filepath); - send_response(fd, "HTTP/1.1 404 NOT FOUND", mime_type, filedata->data, filedata->size); file_free(filedata); @@ -119,9 +138,35 @@ void resp_404(int fd) */ void get_file(int fd, struct cache *cache, char *request_path) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + char filepath[4096]; + struct file_data *filedata; + char *mime_type; + struct cache_entry *ce; + + snprintf(filepath, sizeof filepath, "%s%s", SERVER_ROOT, request_path); + ce = cache_get(cache, filepath); + + if (ce) + { + send_response(fd, "HTTP/1.1 200 OK", ce->content_type, ce->content, ce->content_length); + } + else + { + filedata = file_load(filepath); + + if (filedata == NULL) + { + resp_404(fd); + return; + } + + mime_type = mime_type_get(filepath); + send_response(fd, "HTTP/1.1 200 OK", mime_type, filedata->data, filedata->size); + + cache_put(cache, filepath, mime_type, filedata->data, filedata->size); + + file_free(filedata); + } } /** @@ -132,9 +177,11 @@ void get_file(int fd, struct cache *cache, char *request_path) */ char *find_start_of_body(char *header) { + (void)header; /////////////////// // IMPLEMENT ME! // (Stretch) /////////////////// + return NULL; } /** @@ -148,23 +195,33 @@ void handle_http_request(int fd, struct cache *cache) // Read request int bytes_recvd = recv(fd, request, request_buffer_size - 1, 0); - if (bytes_recvd < 0) { + if (bytes_recvd < 0) + { perror("recv"); return; } - - /////////////////// - // IMPLEMENT ME! // - /////////////////// + char method[50]; + char path[200]; + char HTTP[50]; // Read the three components of the first request line - + sscanf(request, "%s %s %s", method, path, HTTP); // If GET, handle the get endpoints + if (strcmp(method, "GET") == 0) + { - // Check if it's /d20 and handle that special case - // Otherwise serve the requested file by calling get_file() - + // Check if it's /d20 and handle that special case + if (strncmp(path, "/d20", 4) == 0) + { + get_d20(fd); + } + else + { + // Otherwise serve the requested file by calling get_file() + get_file(fd, cache, path); + } + } // (Stretch) If POST, handle the post request } @@ -174,7 +231,7 @@ void handle_http_request(int fd, struct cache *cache) */ int main(void) { - int newfd; // listen on sock_fd, new connection on newfd + int newfd; // listen on sock_fd, new connection on newfd struct sockaddr_storage their_addr; // connector's address information char s[INET6_ADDRSTRLEN]; @@ -182,8 +239,8 @@ int main(void) // Get a listening socket int listenfd = get_listener_socket(PORT); - - if (listenfd < 0) { + if (listenfd < 0) + { fprintf(stderr, "webserver: fatal error getting listening socket\n"); exit(1); } @@ -193,29 +250,32 @@ int main(void) // This is the main loop that accepts incoming connections and // forks a handler process to take care of it. The main parent // process then goes back to waiting for new connections. - - while(1) { + while (1) + { + // puts(">> 1"); socklen_t sin_size = sizeof their_addr; - // Parent process will block on the accept() call until someone // makes a new connection: + // puts(">> 2"); newfd = accept(listenfd, (struct sockaddr *)&their_addr, &sin_size); - if (newfd == -1) { + + if (newfd == -1) + { perror("accept"); continue; } - // Print out a message that we got the connection + // puts(">> 3"); inet_ntop(their_addr.ss_family, - get_in_addr((struct sockaddr *)&their_addr), - s, sizeof s); + get_in_addr((struct sockaddr *)&their_addr), + s, sizeof s); printf("server: got connection from %s\n", s); - + // newfd is a new socket descriptor for the new connection. // listenfd is still listening for new connections. - + // puts(">> 4"); handle_http_request(newfd, cache); - + // puts(">> 5"); close(newfd); } @@ -223,4 +283,3 @@ int main(void) return 0; } - diff --git a/src/serverroot/index.html b/src/serverroot/index.html index 0ecbe58fe..972373256 100644 --- a/src/serverroot/index.html +++ b/src/serverroot/index.html @@ -9,5 +9,7 @@