diff --git a/example/sprintf_example b/example/sprintf_example new file mode 100644 index 000000000..97dc5f727 Binary files /dev/null and b/example/sprintf_example differ diff --git a/example/sprintf_example.c b/example/sprintf_example.c new file mode 100644 index 000000000..aff9d6173 --- /dev/null +++ b/example/sprintf_example.c @@ -0,0 +1,35 @@ +#include +#include + +/* + HTTP/1.1 200 OK\n + Date: .... <--- would be here but not in this example + Content-Type: text/html\n + Content-Length: %d\n + Connection: close\n + \n <--- needs to be a new line that separates header from body + +*/ + +// Example of how to build a response +int main(void) +{ + // buffer to hold the res data + char response[500000]; + + char *body = "

Hello, world!

"; + int length = strlen(body); + + // Let's build the actual response now + sprintf(response, + "HTTP/1.1 200 OK\n" + "Content-Type: text/html\n" + "Content-Length: %d\n" + "Connection: close\n" + "\n" + "%s", + length, body); + + printf("%s", response); + return 0; +} \ No newline at end of file diff --git a/example/sscanf_example b/example/sscanf_example new file mode 100644 index 000000000..3b74e9b1f Binary files /dev/null and b/example/sscanf_example differ diff --git a/example/sscanf_example.c b/example/sscanf_example.c new file mode 100644 index 000000000..8bacbbfd1 --- /dev/null +++ b/example/sscanf_example.c @@ -0,0 +1,27 @@ +#include + +/* + given this request, how do we parse (extract important parts of it) it? + GET /foobar HTTP/1.1 + Host: www.example.com + Connection: close + +*/ + +int main(void) +{ + char *s = "GET /foobar HTTP/1.1\nHost: www.example.com\nConnection: close\n"; + + // buffer to hold the method + char method[200]; + // buffer to hold the path + char path[8192]; + + // take some string, supply a format specifier + // %s %s pulls out the first two strings which will be the GET and path + sscanf(s, "%s %s", method, path); + + printf("method: %s\n", method); + printf("path: %s\n", path); + return 0; +} \ No newline at end of file diff --git a/src/cache.c b/src/cache.c index c72975cdd..fc15ee707 100644 --- a/src/cache.c +++ b/src/cache.c @@ -3,15 +3,29 @@ #include #include "hashtable.h" #include "cache.h" - +// day 2 stuff /** * Allocate a cache entry */ struct cache_entry *alloc_entry(char *path, char *content_type, void *content, int content_length) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // free spaces for cache entry + struct cache_entry *new_entry = malloc(sizeof(struct cache_entry)); + + // deep copy path + new_entry->path = malloc(strlen(path) + 1); + strcpy(new_entry->path, path); + // deep copy content type + new_entry->content_type = malloc(strlen(content_type) + 1); + strcpy(new_entry->content_type, content_type); + // deep copy content length + new_entry->content_length = malloc(strlen(content_length) + 1); + memcpy(new_entry->content_length, content, content_length); + + new_entry->content_length = content_length; + new_entry->prev = new_entry->next = NULL; + + return new_entry; } /** @@ -19,9 +33,12 @@ struct cache_entry *alloc_entry(char *path, char *content_type, void *content, i */ void free_entry(struct cache_entry *entry) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // free entry struct properties + free(entry->content); + free(entry->content_length); + free(entry->content_type); + // free the struct itself + free(entry); } /** @@ -30,10 +47,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 +66,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,10 +88,9 @@ void dllist_move_to_head(struct cache *cache, struct cache_entry *ce) } } - /** * Removes the tail from the list and returns it - * + * * NOTE: does not deallocate the tail */ struct cache_entry *dllist_remove_tail(struct cache *cache) @@ -85,15 +107,21 @@ struct cache_entry *dllist_remove_tail(struct cache *cache) /** * Create a new cache - * + * * max_size: maximum number of entries in the cache * hashsize: hashtable size (0 for default) */ struct cache *cache_create(int max_size, int hashsize) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + struct cache *new_cache = malloc(sizeof(struct cache)); + struct hashtable *hash_table = hashtable_create(hashsize, NULL); + + new_cache->head = new_cache->tail = NULL; + new_cache->cur_size = 0; + new_cache->max_size = max_size; + new_cache->index = hash_table; + + return new_cache; } void cache_free(struct cache *cache) @@ -102,7 +130,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); @@ -117,14 +146,38 @@ void cache_free(struct cache *cache) * Store an entry in the cache * * This will also remove the least-recently-used items as necessary. - * + * * NOTE: doesn't check for duplicate cache entries */ void cache_put(struct cache *cache, char *path, char *content_type, void *content, int content_length) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // Allocate a new cache entry + struct cache_entry *new_entry = alloc_entry(path, content_type, content, content_length); + + // Insert the entry at the head of the dll + dllist_insert_head(cache, new_entry); + + // Store the entry into the hashtable + hashtable_put(cache->index, path, new_entry); + + // Increment the current size of the cache + cache->cur_size += 1; + + // If the cache size is greater than max size + if (cache->cur_size > cache->max_size) + { + // Remove the entry from dll tail + struct cache_entry *old_entry = dllist_remove_tail(cache); + // remove the entry from hash + hashtable_delete(cache->index, old_entry->path); + // Free the cache entry + free_entry(old_entry); + // bring down current size to be no bigger than max size + if (cache->cur_size > cache->max_size) + { + cache->cur_size -= 1; + } + } } /** @@ -132,7 +185,14 @@ 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! // - /////////////////// + // if entry at index is NULL + if (hashtable_get(cache->index, path) == NULL) + { + return NULL; + } + else + { // Move entry to head of the dll + dllist_move_to_head(cache, hashtable_get(cache->index, path)); + return cache->head; + } } diff --git a/src/cache.h b/src/cache.h index f64c976ad..7432d6ac7 100644 --- a/src/cache.h +++ b/src/cache.h @@ -2,8 +2,9 @@ #define _WEBCACHE_H_ // Individual hash table entry -struct cache_entry { - char *path; // Endpoint path--key to the cache +struct cache_entry +{ // each entry is a node + char *path; // Endpoint path--key to the cache char *content_type; int content_length; void *content; @@ -12,11 +13,12 @@ struct cache_entry { }; // A cache -struct cache { +struct cache +{ struct hashtable *index; struct cache_entry *head, *tail; // Doubly-linked list - int max_size; // Maxiumum number of entries - int cur_size; // Current number of entries + int max_size; // Maxiumum number of entries + int cur_size; // Current number of entries }; extern struct cache_entry *alloc_entry(char *path, char *content_type, void *content, int content_length); diff --git a/src/server.c b/src/server.c index ea43306fc..d45ff4635 100644 --- a/src/server.c +++ b/src/server.c @@ -1,18 +1,18 @@ /** * webserver.c -- A webserver written in C - * + * * Test with curl (if you don't have it, install it): - * + * * curl -D - http://localhost:3490/ * curl -D - http://localhost:3490/d20 * curl -D - http://localhost:3490/date - * + * * You can also test the above URLs in your browser! They should work! - * + * * Posting Data: - * + * * curl -D - -X POST -H 'Content-Type: text/plain' -d 'Hello, sample data!' http://localhost:3490/save - * + * * (Posting data is harder to test from a browser.) */ @@ -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" @@ -45,63 +45,84 @@ * header: "HTTP/1.1 404 NOT FOUND" or "HTTP/1.1 200 OK", etc. * content_type: "text/plain", etc. * body: the data to send. - * + * * Return the value from the send() function. */ int send_response(int fd, char *header, char *content_type, void *body, int content_length) { - const int max_response_size = 262144; - char response[max_response_size]; - - // Build HTTP response and store it in response - - /////////////////// - // IMPLEMENT ME! // - /////////////////// + const int max_res_size = 262144; + char res[max_res_size]; + int res_length = 0; + time_t t = time(NULL); // Unix timestamp + + (void)header; + (void)content_type; + (void)body; + (void)content_length; + // Build HTTP res and store it in res + + res_length = sprintf(res, + "%s\n" // header + "Date: %s" // date + "Connection: close\n" // close + "Content-Length: %d\n" // length + "Content-Type: %s\n" // type + "\n", // mandatory space + header, + asctime(gmtime(&t)), + content_length, + content_type); + + memcpy(res + res_length, body, content_length); // Send it all! - int rv = send(fd, response, response_length, 0); + int rv = send(fd, res, res_length + content_length, 0); - if (rv < 0) { + if (rv < 0) + { perror("send"); } + rv = send(fd, body, content_length, 0); + return rv; } - /** * Send a /d20 endpoint response */ void get_d20(int fd) { // Generate a random number between 1 and 20 inclusive - - /////////////////// - // IMPLEMENT ME! // - /////////////////// + int body = (rand() % 20) + 1; - // Use send_response() to send it back as text/plain data + char *status = "HTTP/1.1 200 OK\n"; + char *type = "text/plain\n"; + char res_body[20]; /////////////////// // IMPLEMENT ME! // /////////////////// + sprintf(res_body, "%d\n", body); + // Use send_response() to send it back as text/plain data + send_response(fd, status, type, res_body, strlen(res_body)); } /** - * Send a 404 response + * Send a 404 response || already implamented */ 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); @@ -116,26 +137,51 @@ void resp_404(int fd) /** * Read and return a file from disk or cache + * get file will look similar to response 404 */ void get_file(int fd, struct cache *cache, char *request_path) { /////////////////// // IMPLEMENT ME! // /////////////////// + char filepath[4096]; + struct file_data *filedata; + (void)cache; + + // construct the full file path + snprintf(filepath, sizeof filepath, "%s%s", SERVER_ROOT, request_path); + // can also use sprintf if you're feeling lazy + // sprintf(filepath, "%s%s", SERVER_ROOT, request_path); + filedata = file_load(filepath); + + // check to see if file_load returned a valid file + if (filedata == NULL) + { + resp_404(fd); + return; + } + + char *mime_type = mime_type_get(filepath); + // we fetched a valid file + // make sure we send it! + send_response(fd, "HTTP/1.1 200 OK", mime_type, filedata->data, filedata->size); + + // free file data struct after it's been sent + file_free(filedata); } /** * Search for the end of the HTTP header - * + * * "Newlines" in HTTP can be \r\n (carriage return followed by newline) or \n * (newline) or \r (carriage return). */ -char *find_start_of_body(char *header) -{ - /////////////////// - // IMPLEMENT ME! // (Stretch) - /////////////////// -} +// char *find_start_of_body(char *header) +// { +// /////////////////// +// // IMPLEMENT ME! // (Stretch) +// /////////////////// +// } /** * Handle HTTP request and send response @@ -144,27 +190,40 @@ void handle_http_request(int fd, struct cache *cache) { const int request_buffer_size = 65536; // 64K char request[request_buffer_size]; + char request_type[8]; + char request_path[1024]; // Read request int bytes_recvd = recv(fd, request, request_buffer_size - 1, 0); - if (bytes_recvd < 0) { + if (bytes_recvd < 0) + { perror("recv"); return; } + (void)cache; /////////////////// // IMPLEMENT ME! // /////////////////// // Read the three components of the first request line + sscanf(request, "%s %s", request_type, request_path); - // If GET, handle the get endpoints - - // Check if it's /d20 and handle that special case - // Otherwise serve the requested file by calling get_file() + printf("REQUEST: %s %s\n", request_type, request_path); + // If GET, handle the get endpoints + if (strcmp(request_type, "GET") == 0 && strcmp(request_path, "/d20") == 0) + { + // Check if it's /d20 and handle that special case + get_d20(fd); + } + else + { + // Otherwise serve the requested file by calling get_file() + get_file(fd, cache, request_path); + } // (Stretch) If POST, handle the post request } @@ -174,7 +233,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]; @@ -183,7 +242,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,24 +253,26 @@ 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) + { socklen_t sin_size = sizeof their_addr; // Parent process will block on the accept() call until someone // makes a new connection: 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 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. @@ -223,4 +285,3 @@ int main(void) return 0; } -