From fa9fc693b8b5bcdfc6d6e5ea37ddd409d6a57f83 Mon Sep 17 00:00:00 2001 From: Beej Jorgensen Date: Fri, 28 Sep 2018 15:19:03 -0700 Subject: [PATCH] new skeletonized --- .gitignore | 3 +- src/Makefile | 32 ++++- src/cache.c | 121 ++++++++++++++++ src/cache.h | 25 ++++ src/file.c | 75 ++++++++++ src/file.h | 12 ++ src/hashtable.c | 288 +++++++++++++++++++++++++++++++++++++ src/hashtable.h | 22 +++ src/llist.c | 243 +++++++++++++++++++++++++++++++ src/llist.h | 23 +++ src/mime.c | 46 ++++++ src/mime.h | 6 + src/net.c | 107 ++++++++++++++ src/net.h | 7 + src/server.c | 376 ++++++++++++++++-------------------------------- 15 files changed, 1129 insertions(+), 257 deletions(-) create mode 100644 src/cache.c create mode 100644 src/cache.h create mode 100644 src/file.c create mode 100644 src/file.h create mode 100644 src/hashtable.c create mode 100644 src/hashtable.h create mode 100644 src/llist.c create mode 100644 src/llist.h create mode 100644 src/mime.c create mode 100644 src/mime.h create mode 100644 src/net.c create mode 100644 src/net.h diff --git a/.gitignore b/.gitignore index 3677e7f48..16ca4d860 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode *.dSYM src/server -src/data.txt \ No newline at end of file +src/data.txt +*.o \ No newline at end of file diff --git a/src/Makefile b/src/Makefile index d6893c963..ef4d36217 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,2 +1,30 @@ -server: server.c - gcc -Wall -Wextra -g -o $@ $^ +OBJS=server.o net.o file.o mime.o cache.o hashtable.o llist.o + +server: $(OBJS) + gcc -Wall -Wextra -o $@ $^ + +net.o: net.c net.h + gcc -Wall -Wextra -c $< + +server.o: server.c net.h + gcc -Wall -Wextra -c $< + +file.o: file.c file.h + gcc -Wall -Wextra -c $< + +mime.o: mime.c mime.h + gcc -Wall -Wextra -c $< + +cache.o: cache.c cache.h + gcc -Wall -Wextra -c $< + +hashtable.o: hashtable.c hashtable.h + gcc -Wall -Wextra -c $< + +llist.o: llist.c llist.h + gcc -Wall -Wextra -c $< + +.PHONY: clean + +clean: + rm -f $(OBJS) diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 000000000..0a91047c4 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "hashtable.h" +#include "cache.h" + +/** + * Allocate a cache entry + */ +struct cache_entry *alloc_entry(char *path, char *content_type, void *content, int content_length) +{ + /////////////////// + // IMPLEMENT ME! // + /////////////////// +} + +/** + * Deallocate a cache entry + */ +void free_entry(void *v_ent, void *varg) +{ + /////////////////// + // IMPLEMENT ME! // + /////////////////// +} + +/** + * Insert a cache entry at the head of the linked list + */ +void dllist_insert_head(struct cache *cache, struct cache_entry *ce) +{ + // Insert at the head of the list + if (cache->head == NULL) { + cache->head = cache->tail = ce; + ce->prev = ce->next = NULL; + } else { + cache->head->prev = ce; + ce->next = cache->head; + ce->prev = NULL; + cache->head = ce; + } +} + +/** + * Move a cache entry to the head of the list + */ +void dllist_move_to_head(struct cache *cache, struct cache_entry *ce) +{ + if (ce != cache->head) { + if (ce == cache->tail) { + // We're the tail + cache->tail = ce->prev; + cache->tail->next = NULL; + + } else { + // We're neither the head nor the tail + ce->prev->next = ce->next; + ce->next->prev = ce->prev; + } + + ce->next = cache->head; + cache->head->prev = ce; + ce->prev = NULL; + cache->head = 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) +{ + struct cache_entry *oldtail = cache->tail; + + cache->tail = oldtail->prev; + cache->tail->next = NULL; + + cache->cur_size--; + + return oldtail; +} + +/** + * 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! // + /////////////////// +} + +/** + * 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! // + /////////////////// +} + +/** + * Retrieve an entry from the cache + */ +struct cache_entry *cache_get(struct cache *cache, char *path) +{ + /////////////////// + // IMPLEMENT ME! // + /////////////////// +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 000000000..95290d0e9 --- /dev/null +++ b/src/cache.h @@ -0,0 +1,25 @@ +#ifndef _WEBCACHE_H_ +#define _WEBCACHE_H_ + +// Individual hash table entry +struct cache_entry { + /////////////////// + // IMPLEMENT ME! // + /////////////////// + + struct cache_entry *prev, *next; // Doubly-linked list +}; + +// A 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 +}; + +extern struct cache *cache_create(int max_size, int hashsize); +extern void cache_put(struct cache *cache, char *path, char *content_type, void *content, int content_length); +extern struct cache_entry *cache_get(struct cache *cache, char *path); + +#endif \ No newline at end of file diff --git a/src/file.c b/src/file.c new file mode 100644 index 000000000..5277a92f4 --- /dev/null +++ b/src/file.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include "file.h" + +/** + * Loads a file into memory and returns a pointer to the data. + * + * Buffer is not NUL-terminated. + */ +struct file_data *file_load(char *filename) +{ + char *buffer, *p; + struct stat buf; + int bytes_read, bytes_remaining, total_bytes = 0; + + // Get the file size + if (stat(filename, &buf) == -1) { + return NULL; + } + + // Make sure it's a regular file + if (!(buf.st_mode & S_IFREG)) { + return NULL; + } + + // Open the file for reading + FILE *fp = fopen(filename, "rb"); + + if (fp == NULL) { + return NULL; + } + + // Allocate that many bytes + bytes_remaining = buf.st_size; + p = buffer = malloc(bytes_remaining); + + if (buffer == NULL) { + return NULL; + } + + // Read in the entire file + while (bytes_read = fread(p, 1, bytes_remaining, fp), bytes_read != 0 && bytes_remaining > 0) { + if (bytes_read == -1) { + free(buffer); + return NULL; + } + + bytes_remaining -= bytes_read; + p += bytes_read; + total_bytes += bytes_read; + } + + // Allocate the file data struct + struct file_data *filedata = malloc(sizeof *filedata); + + if (filedata == NULL) { + free(buffer); + return NULL; + } + + filedata->data = buffer; + filedata->size = total_bytes; + + return filedata; +} + +/** + * Frees memory allocated by file_load(). + */ +void file_free(struct file_data *filedata) +{ + free(filedata->data); + free(filedata); +} \ No newline at end of file diff --git a/src/file.h b/src/file.h new file mode 100644 index 000000000..aeea2a186 --- /dev/null +++ b/src/file.h @@ -0,0 +1,12 @@ +#ifndef _FILE_H_ +#define _FILE_H_ + +struct file_data { + int size; + void *data; +}; + +extern struct file_data *file_load(char *filename); +extern void file_free(struct file_data *filedata); + +#endif \ No newline at end of file diff --git a/src/hashtable.c b/src/hashtable.c new file mode 100644 index 000000000..d19aa86b5 --- /dev/null +++ b/src/hashtable.c @@ -0,0 +1,288 @@ +/* + +Example: + +// Create a hash table of 128 elements and use the default hash function + +struct hashtable *ht = hashtable_create(128, NULL); + +int data1 = 12; +int data2 = 30; + +// Store pointers to data in the hash table +// (Data can be pointers to any type of data) + +hashtable_put(ht, "some data", &data1); +hashtable_put(ht, "other data", &data2); + +// Retrieve data + +int *result = hashtable_get(ht, "other data"); +printf("%d\n", *r1); // prints 30 + +// Store a struct: + +struct foo *p = malloc(sizeof *p); + +p->bar = 12; +p->baz = "Hello"; + +hashtable_put(ht, "mystruct", p); + +struct foo *q = hashtable_get("mystruct"); + +printf("%d %s\n", q->bar, q->baz); // 12 Hello + +*/ + +#include +#include +#include +#include "llist.h" +#include "hashtable.h" + +#define DEFAULT_SIZE 128 +#define DEFAULT_GROW_FACTOR 2 + +// Hash table entry +struct htent { + void *key; + int key_size; + int hashed_key; + void *data; +}; + +// Used to cleanup the linked lists +struct foreach_callback_payload { + void *arg; + void (*f)(void *, void *); +}; + +/** + * Change the entry count, maintain load metrics + */ +void add_entry_count(struct hashtable *ht, int d) +{ + ht->num_entries += d; + ht->load = (float)ht->num_entries / ht->size; +} + +/** + * Default modulo hashing function + */ +int default_hashf(void *data, int data_size, int bucket_count) +{ + const int R = 31; // Small prime + int h = 0; + unsigned char *p = data; + + for (int i = 0; i < data_size; i++) { + h = (R * h + p[i]) % bucket_count; + } + + return h; +} + +/** + * Create a new hashtable + */ +struct hashtable *hashtable_create(int size, int (*hashf)(void *, int, int)) +{ + if (size < 1) { + size = DEFAULT_SIZE; + } + + if (hashf == NULL) { + hashf = default_hashf; + } + + struct hashtable *ht = malloc(sizeof *ht); + + if (ht == NULL) return NULL; + + ht->size = size; + ht->num_entries = 0; + ht->load = 0; + ht->bucket = malloc(size * sizeof(struct llist *)); + ht->hashf = hashf; + + for (int i = 0; i < size; i++) { + ht->bucket[i] = llist_create(); + } + + return ht; +} + +/** + * Free an htent + */ +void htent_free(void *htent, void *arg) +{ + (void)arg; + + free(htent); +} + +/** + * Destroy a hashtable + * + * NOTE: does *not* free the data pointer + */ +void hashtable_destroy(struct hashtable *ht) +{ + for (int i = 0; i < ht->size; i++) { + struct llist *llist = ht->bucket[i]; + + llist_foreach(llist, htent_free, NULL); + llist_destroy(llist); + } + + free(ht); +} + +/** + * Put to hash table with a string key + */ +void *hashtable_put(struct hashtable *ht, char *key, void *data) +{ + return hashtable_put_bin(ht, key, strlen(key), data); +} + +/** + * Put to hash table with a binary key + */ +void *hashtable_put_bin(struct hashtable *ht, void *key, int key_size, void *data) +{ + int index = ht->hashf(key, key_size, ht->size); + + struct llist *llist = ht->bucket[index]; + + struct htent *ent = malloc(sizeof *ent); + ent->key = malloc(key_size); + memcpy(ent->key, key, key_size); + ent->key_size = key_size; + ent->hashed_key = index; + ent->data = data; + + if (llist_append(llist, ent) == NULL) { + free(ent->key); + free(ent); + return NULL; + } + + add_entry_count(ht, +1); + + return data; +} + +/** + * Comparison function for hashtable entries + */ +int htcmp(void *a, void *b) +{ + struct htent *entA = a, *entB = b; + + int size_diff = entB->key_size - entA->key_size; + + if (size_diff) { + return size_diff; + } + + return memcmp(entA->key, entB->key, entA->key_size); +} + +/** + * Get from the hash table with a string key + */ +void *hashtable_get(struct hashtable *ht, char *key) +{ + return hashtable_get_bin(ht, key, strlen(key)); +} + +/** + * Get from the hash table with a binary data key + */ +void *hashtable_get_bin(struct hashtable *ht, void *key, int key_size) +{ + int index = ht->hashf(key, key_size, ht->size); + + struct llist *llist = ht->bucket[index]; + + struct htent cmpent; + cmpent.key = key; + cmpent.key_size = key_size; + + struct htent *n = llist_find(llist, &cmpent, htcmp); + + if (n == NULL) { return NULL; } + + return n->data; +} + +/** + * Delete from the hashtable by string key + */ +void *hashtable_delete(struct hashtable *ht, char *key) +{ + return hashtable_delete_bin(ht, key, strlen(key)); +} + +/** + * Delete from the hashtable by binary key + * + * NOTE: does *not* free the data--just free's the hash table entry + */ +void *hashtable_delete_bin(struct hashtable *ht, void *key, int key_size) +{ + int index = ht->hashf(key, key_size, ht->size); + + struct llist *llist = ht->bucket[index]; + + struct htent cmpent; + cmpent.key = key; + cmpent.key_size = key_size; + + struct htent *ent = llist_delete(llist, &cmpent, htcmp); + + if (ent == NULL) { + return NULL; + } + + void *data = ent->data; + + free(ent); + + add_entry_count(ht, -1); + + return data; +} + +/** + * Foreach callback function + */ +void foreach_callback(void *vent, void *vpayload) +{ + struct htent *ent = vent; + struct foreach_callback_payload *payload = vpayload; + + payload->f(ent->data, payload->arg); +} + +/** + * For-each element in the hashtable + * + * Note: elements are returned in effectively random order. + */ +void hashtable_foreach(struct hashtable *ht, void (*f)(void *, void *), void *arg) +{ + struct foreach_callback_payload payload; + + payload.f = f; + payload.arg = arg; + + for (int i = 0; i < ht->size; i++) { + struct llist *llist = ht->bucket[i]; + + llist_foreach(llist, foreach_callback, &payload); + } +} diff --git a/src/hashtable.h b/src/hashtable.h new file mode 100644 index 000000000..2bf02e985 --- /dev/null +++ b/src/hashtable.h @@ -0,0 +1,22 @@ +#ifndef _HASHTABLE_H_ +#define _HASHTABLE_H_ + +struct hashtable { + int size; // Read-only + int num_entries; // Read-only + float load; // Read-only + struct llist **bucket; + int (*hashf)(void *data, int data_size, int bucket_count); +}; + +extern struct hashtable *hashtable_create(int size, int (*hashf)(void *, int, int)); +extern void hashtable_destroy(struct hashtable *ht); +extern void *hashtable_put(struct hashtable *ht, char *key, void *data); +extern void *hashtable_put_bin(struct hashtable *ht, void *key, int key_size, void *data); +extern void *hashtable_get(struct hashtable *ht, char *key); +extern void *hashtable_get_bin(struct hashtable *ht, void *key, int key_size); +extern void *hashtable_delete(struct hashtable *ht, char *key); +extern void *hashtable_delete_bin(struct hashtable *ht, void *key, int key_size); +extern void hashtable_foreach(struct hashtable *ht, void (*f)(void *, void *), void *arg); + +#endif diff --git a/src/llist.c b/src/llist.c new file mode 100644 index 000000000..5c70ac937 --- /dev/null +++ b/src/llist.c @@ -0,0 +1,243 @@ +#include +#include "llist.h" + +struct llist_node { + void *data; + struct llist_node *next; +}; + +/** + * Allocate a new linked list + */ +struct llist *llist_create(void) +{ + return calloc(1, sizeof(struct llist)); +} + +/** + * Destroy a linked list + * + * If destfn is not NULL, it is called with the node data before the + * node is deallocated. + * + * NOTE: does *not* deallocate the data in each node!! + */ +void llist_destroy(struct llist *llist) +{ + struct llist_node *n = llist->head, *next; + + while (n != NULL) { + next = n->next; + free(n); + + n = next; + } + + free(llist); +} + +/** + * Insert at the head of a linked list + */ +void *llist_insert(struct llist *llist, void *data) +{ + struct llist_node *n = calloc(1, sizeof *n); + + if (n == NULL) { + return NULL; + } + + n->data = data; + n->next = llist->head; + llist->head = n; + + llist->count++; + + return data; +} + +/** + * Append to the end of a list + */ +void *llist_append(struct llist *llist, void *data) +{ + struct llist_node *tail = llist->head; + + // If list is empty, just insert + if (tail == NULL) { + return llist_insert(llist, data); + } + + struct llist_node *n = calloc(1, sizeof *n); + + if (n == NULL) { + return NULL; + } + + while (tail->next != NULL) { + tail = tail->next; + } + + n->data = data; + tail->next = n; + + llist->count++; + + return data; +} + +/** + * Return the first element in a list + */ +void *llist_head(struct llist *llist) +{ + if (llist->head == NULL) { + return NULL; + } + + return llist->head->data; +} + +/** + * Return the last element in a list + */ +void *llist_tail(struct llist *llist) +{ + struct llist_node *n = llist->head; + + if (n == NULL) { + return NULL; + } + + while (n->next != NULL) { + n = n->next; + } + + return n->data; +} + +/** + * Find an element in the list + * + * cmpfn should return 0 if the comparison to this node's data is equal. + */ +void *llist_find(struct llist *llist, void *data, int (*cmpfn)(void *, void *)) +{ + struct llist_node *n = llist->head; + + if (n == NULL) { + return NULL; + } + + while (n != NULL) { + if (cmpfn(data, n->data) == 0) { + break; + } + + n = n->next; + } + + if (n == NULL) { + return NULL; + } + + return n->data; +} + +/** + * Delete an element in the list + * + * cmpfn should return 0 if the comparison to this node's data is equal. + * + * NOTE: does *not* free the data--it merely returns a pointer to it + */ +void *llist_delete(struct llist *llist, void *data, int (*cmpfn)(void *, void *)) +{ + struct llist_node *n = llist->head, *prev = NULL; + + while (n != NULL) { + if (cmpfn(data, n->data) == 0) { + + void *data = n->data; + + if (prev == NULL) { + // Free the head + llist->head = n->next; + free(n); + + } else { + // Free the non-head + prev->next = n->next; + free(n); + } + + llist->count--; + + return data; + } + + prev = n; + n = n->next; + } + + return NULL; +} + +/** + * Return the number of elements in the list + */ +int llist_count(struct llist *llist) +{ + return llist->count; +} + +/** + * For each item in the list run a function + */ +void llist_foreach(struct llist *llist, void (*f)(void *, void *), void *arg) +{ + struct llist_node *p = llist->head, *next; + + while (p != NULL) { + next = p->next; + f(p->data, arg); + p = next; + } +} + +/** + * Allocates and returns a new NULL-terminated array of pointers to data + * elements in the list. + * + * NOTE: This is a read-only array! Consider it an array view onto the linked + * list. + */ +void **llist_array_get(struct llist *llist) +{ + if (llist->head == NULL) { + return NULL; + } + + void **a = malloc(sizeof *a * llist->count + 1); + + struct llist_node *n; + int i; + + for (i = 0, n = llist->head; n != NULL; i++, n = n->next) { + a[i] = n->data; + } + + a[i] = NULL; + + return a; +} + +/** + * Free an array allocated with llist_array_get(). + * + * NOTE: this does not modify the linked list or its data in any way. + */ +void llist_array_free(void **a) +{ + free(a); +} diff --git a/src/llist.h b/src/llist.h new file mode 100644 index 000000000..315105c71 --- /dev/null +++ b/src/llist.h @@ -0,0 +1,23 @@ +#ifndef _LLIST_H_ +#define _LLIST_H_ + +struct llist { + struct llist_node *head; + int count; +}; + +extern struct llist *llist_create(void); +extern void llist_destroy(struct llist *llist); +extern void *llist_insert(struct llist *llist, void *data); +extern void *llist_append(struct llist *llist, void *data); +extern void *llist_head(struct llist *llist); +extern void *llist_tail(struct llist *llist); +extern void *llist_find(struct llist *llist, void *data, int (*cmpfn)(void *, void *)); +extern void *llist_delete(struct llist *llist, void *data, int (*cmpfn)(void *, void *)); +extern int llist_count(struct llist *llist); +extern void llist_foreach(struct llist *llist, void (*f)(void *, void *), void *arg); +extern void **llist_array_get(struct llist *llist); +extern void llist_array_free(void **a); + +#endif + diff --git a/src/mime.c b/src/mime.c new file mode 100644 index 000000000..4013b5ca8 --- /dev/null +++ b/src/mime.c @@ -0,0 +1,46 @@ +#include +#include +#include "mime.h" + +#define DEFAULT_MIME_TYPE "application/octet-stream" + +/** + * Lowercase a string + */ +char *strlower(char *s) +{ + for (char *p = s; *p != '\0'; p++) { + *p = tolower(*p); + } + + return s; +} + +/** + * Return a MIME type for a given filename + */ +char *mime_type_get(char *filename) +{ + char *ext = strrchr(filename, '.'); + + 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"; } + + return DEFAULT_MIME_TYPE; +} \ No newline at end of file diff --git a/src/mime.h b/src/mime.h new file mode 100644 index 000000000..7e6b615be --- /dev/null +++ b/src/mime.h @@ -0,0 +1,6 @@ +#ifndef _MIME_H_ +#define _MIME_H_ + +extern char *mime_type_get(char *filename); + +#endif \ No newline at end of file diff --git a/src/net.c b/src/net.c new file mode 100644 index 000000000..037fcdbf5 --- /dev/null +++ b/src/net.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "net.h" + +#define BACKLOG 10 // how many pending connections queue will hold + +/** + * This gets an Internet address, either IPv4 or IPv6 + * + * Helper function to make printing easier. + */ +void *get_in_addr(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +/** + * Return the main listening socket + * + * Returns -1 or error + */ +int get_listener_socket(char *port) +{ + int sockfd; + struct addrinfo hints, *servinfo, *p; + int yes = 1; + int rv; + + // This block of code looks at the local network interfaces and + // tries to find some that match our requirements (namely either + // IPv4 or IPv6 (AF_UNSPEC) and TCP (SOCK_STREAM) and use any IP on + // this machine (AI_PASSIVE). + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP + + if ((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + // Once we have a list of potential interfaces, loop through them + // and try to set up a socket on each. Quit looping the first time + // we have success. + for(p = servinfo; p != NULL; p = p->ai_next) { + + // Try to make a socket based on this candidate interface + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + //perror("server: socket"); + continue; + } + + // SO_REUSEADDR prevents the "address already in use" errors + // that commonly come up when testing servers. + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) == -1) { + perror("setsockopt"); + close(sockfd); + freeaddrinfo(servinfo); // all done with this structure + return -2; + } + + // See if we can bind this socket to this local IP address. This + // associates the file descriptor (the socket descriptor) that + // we will read and write on with a specific IP address. + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + //perror("server: bind"); + continue; + } + + // If we got here, we got a bound socket and we're done + break; + } + + freeaddrinfo(servinfo); // all done with this structure + + // If p is NULL, it means we didn't break out of the loop, above, + // and we don't have a good socket. + if (p == NULL) { + fprintf(stderr, "webserver: failed to find local address\n"); + return -3; + } + + // Start listening. This is what allows remote computers to connect + // to this socket/IP. + if (listen(sockfd, BACKLOG) == -1) { + //perror("listen"); + close(sockfd); + return -4; + } + + return sockfd; +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 000000000..4f0721308 --- /dev/null +++ b/src/net.h @@ -0,0 +1,7 @@ +#ifndef _NET_H_ +#define _NET_H_ + +void *get_in_addr(struct sockaddr *sa); +int get_listener_socket(char *port); + +#endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index 3d3a876b9..01ded1dd6 100644 --- a/src/server.c +++ b/src/server.c @@ -26,155 +26,18 @@ #include #include #include -#include -#include #include #include #include +#include "net.h" +#include "file.h" +#include "mime.h" +#include "cache.h" #define PORT "3490" // the port users will be connecting to -#define BACKLOG 10 // how many pending connections queue will hold - -/** - * Handle SIGCHILD signal - * - * We get this signal when a child process dies. This function wait()s for - * Zombie processes. - * - * This is only necessary if we've implemented a multiprocessed version with - * fork(). - */ -void sigchld_handler(int s) { - (void)s; // quiet unused variable warning - - // waitpid() might overwrite errno, so we save and restore it: - int saved_errno = errno; - - // Wait for all children that have died, discard the exit status - while(waitpid(-1, NULL, WNOHANG) > 0); - - errno = saved_errno; -} - -/** - * Set up a signal handler that listens for child processes to die so - * they can be reaped with wait() - * - * Whenever a child process dies, the parent process gets signal - * SIGCHLD; the handler sigchld_handler() takes care of wait()ing. - * - * This is only necessary if we've implemented a multiprocessed version with - * fork(). - */ -void start_reaper(void) -{ - struct sigaction sa; - - sa.sa_handler = sigchld_handler; // Reap all dead processes - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; // Restart signal handler if interrupted - if (sigaction(SIGCHLD, &sa, NULL) == -1) { - perror("sigaction"); - exit(1); - } -} - -/** - * This gets an Internet address, either IPv4 or IPv6 - * - * Helper function to make printing easier. - */ -void *get_in_addr(struct sockaddr *sa) -{ - if (sa->sa_family == AF_INET) { - return &(((struct sockaddr_in*)sa)->sin_addr); - } - - return &(((struct sockaddr_in6*)sa)->sin6_addr); -} - -/** - * Return the main listening socket - * - * Returns -1 or error - */ -int get_listener_socket(char *port) -{ - int sockfd; - struct addrinfo hints, *servinfo, *p; - int yes=1; - int rv; - - // This block of code looks at the local network interfaces and - // tries to find some that match our requirements (namely either - // IPv4 or IPv6 (AF_UNSPEC) and TCP (SOCK_STREAM) and use any IP on - // this machine (AI_PASSIVE). - - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; // use my IP - - if ((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); - return -1; - } - - // Once we have a list of potential interfaces, loop through them - // and try to set up a socket on each. Quit looping the first time - // we have success. - for(p = servinfo; p != NULL; p = p->ai_next) { - - // Try to make a socket based on this candidate interface - if ((sockfd = socket(p->ai_family, p->ai_socktype, - p->ai_protocol)) == -1) { - //perror("server: socket"); - continue; - } - - // SO_REUSEADDR prevents the "address already in use" errors - // that commonly come up when testing servers. - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, - sizeof(int)) == -1) { - perror("setsockopt"); - close(sockfd); - freeaddrinfo(servinfo); // all done with this structure - return -2; - } - - // See if we can bind this socket to this local IP address. This - // associates the file descriptor (the socket descriptor) that - // we will read and write on with a specific IP address. - if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { - close(sockfd); - //perror("server: bind"); - continue; - } - - // If we got here, we got a bound socket and we're done - break; - } - - freeaddrinfo(servinfo); // all done with this structure - - // If p is NULL, it means we didn't break out of the loop, above, - // and we don't have a good socket. - if (p == NULL) { - fprintf(stderr, "webserver: failed to find local address\n"); - return -3; - } - - // Start listening. This is what allows remote computers to connect - // to this socket/IP. - if (listen(sockfd, BACKLOG) == -1) { - //perror("listen"); - close(sockfd); - return -4; - } - - return sockfd; -} +#define SERVER_FILES "./serverfiles" +#define SERVER_ROOT "./serverroot" /** * Send an HTTP response @@ -185,114 +48,124 @@ int get_listener_socket(char *port) * * Return the value from the send() function. */ -int send_response(int fd, char *header, char *content_type, char *body) +int send_response(int fd, char *header, char *content_type, void *body, int content_length) { - const int max_response_size = 65536; - char response[max_response_size]; - int response_length; // Total length of header plus body - - // !!!! IMPLEMENT ME + const int max_response_size = 65536; + char response[max_response_size]; - // Send it all! - int rv = send(fd, response, response_length, 0); + // Build HTTP response and store it in response - if (rv < 0) { - perror("send"); - } + /////////////////// + // IMPLEMENT ME! // + /////////////////// - return rv; -} + // Send it all! + int rv = send(fd, response, response_length, 0); + if (rv < 0) { + perror("send"); + } -/** - * Send a 404 response - */ -void resp_404(int fd) -{ - send_response(fd, "HTTP/1.1 404 NOT FOUND", "text/html", "

404 Page Not Found

"); + return rv; } -/** - * Send a / endpoint response - */ -void get_root(int fd) -{ - // !!!! IMPLEMENT ME - //send_response(... -} /** * Send a /d20 endpoint response */ void get_d20(int fd) { - // !!!! IMPLEMENT ME + // Generate a random number between 1 and 20 inclusive + + /////////////////// + // IMPLEMENT ME! // + /////////////////// + + // Use send_response() to send it back as text/plain data + + /////////////////// + // IMPLEMENT ME! // + /////////////////// } /** - * Send a /date endpoint response + * Send a 404 response */ -void get_date(int fd) +void resp_404(int fd) { - // !!!! IMPLEMENT ME + char filepath[4096]; + struct file_data *filedata; + char *mime_type; + + snprintf(filepath, sizeof filepath, "%s/404.html", SERVER_FILES); + filedata = file_load(filepath); + + 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); } /** - * Post /save endpoint data + * Read and return a file from disk or cache */ -void post_save(int fd, char *body) +void get_file(int fd, struct cache *cache, char *request_path) { - // !!!! IMPLEMENT ME - - // Save the body and send a response + /////////////////// + // IMPLEMENT ME! // + /////////////////// } /** - * Search for the start of the HTTP body. - * - * The body is after the header, separated from it by a blank line (two newlines - * in a row). - * + * 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 + /////////////////// + // IMPLEMENT ME! // (Stretch) + /////////////////// } /** * Handle HTTP request and send response */ -void handle_http_request(int fd) +void handle_http_request(int fd, struct cache *cache) { - const int request_buffer_size = 65536; // 64K - char request[request_buffer_size]; - char *p; - char request_type[8]; // GET or POST - char request_path[1024]; // /info etc. - char request_protocol[128]; // HTTP/1.1 + const int request_buffer_size = 65536; // 64K + char request[request_buffer_size]; - // Read request - int bytes_recvd = recv(fd, request, request_buffer_size - 1, 0); + // Read request + int bytes_recvd = recv(fd, request, request_buffer_size - 1, 0); - if (bytes_recvd < 0) { - perror("recv"); - return; - } + if (bytes_recvd < 0) { + perror("recv"); + return; + } - // NUL terminate request string - request[bytes_recvd] = '\0'; - // !!!! IMPLEMENT ME - // Get the request type and path from the first line - // Hint: sscanf()! + /////////////////// + // IMPLEMENT ME! // + /////////////////// - // !!!! IMPLEMENT ME (stretch goal) - // find_start_of_body() + // Read the three components of the first request line - // !!!! IMPLEMENT ME - // call the appropriate handler functions, above, with the incoming data + // 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() + + + // (Stretch) If POST, handle the post request } /** @@ -300,58 +173,53 @@ void handle_http_request(int fd) */ int main(void) { - int newfd; // listen on sock_fd, new connection on newfd - struct sockaddr_storage their_addr; // connector's address information - char s[INET6_ADDRSTRLEN]; - - // Start reaping child processes - start_reaper(); - - // Get a listening socket - int listenfd = get_listener_socket(PORT); - - if (listenfd < 0) { - fprintf(stderr, "webserver: fatal error getting listening socket\n"); - exit(1); - } - - printf("webserver: waiting for connections...\n"); - - // This is the main loop that accepts incoming connections and - // fork()s a handler process to take care of it. The main parent - // process then goes back to waiting for new connections. - - 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) { - perror("accept"); - continue; - } + int newfd; // listen on sock_fd, new connection on newfd + struct sockaddr_storage their_addr; // connector's address information + char s[INET6_ADDRSTRLEN]; - // 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); - 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. + struct cache *cache = cache_create(10, 0); - // !!!! IMPLEMENT ME (stretch goal) - // Convert this to be multiprocessed with fork() + // Get a listening socket + int listenfd = get_listener_socket(PORT); - handle_http_request(newfd); + if (listenfd < 0) { + fprintf(stderr, "webserver: fatal error getting listening socket\n"); + exit(1); + } + + printf("webserver: waiting for connections on port %s...\n", PORT); - // Done with this - close(newfd); - } + // This is the main loop that accepts incoming connections and + // fork()s a handler process to take care of it. The main parent + // process then goes back to waiting for new connections. + + 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) { + 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); + 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. + + handle_http_request(newfd, cache); + + close(newfd); + } - // Unreachable code + // Unreachable code - return 0; + return 0; }