Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kevin Sooter - C-Web-Server #300

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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`.

Expand Down Expand Up @@ -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`.

Expand All @@ -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

Expand Down Expand Up @@ -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.

102 changes: 79 additions & 23 deletions src/cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,34 @@
*/
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;
}

/**
* Deallocate a cache entry
*/
void free_entry(struct cache_entry *entry)
{
///////////////////
// IMPLEMENT ME! //
///////////////////
free(entry->path);
free(entry->content_type);
free(entry->content);
free(entry);
}

/**
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
*
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -122,17 +153,42 @@ 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);
}
}

/**
* Retrieve an entry from the cache
*/
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;
}
49 changes: 37 additions & 12 deletions src/mime.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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;
}
Loading