diff --git a/QUESTION.md b/QUESTION.md new file mode 100644 index 000000000..d0da4182f --- /dev/null +++ b/QUESTION.md @@ -0,0 +1,9 @@ +- content-type을 "text/plain"으로 설정하면 파일 다운로드가 되네? + +- content-length가 안 맞으면 화면(브라우저)에서 오류나네 + +- rest 응답같은건 어떤 content-type으로 해야 하는지? + + application/json으로 하면 될 줄 알았는데 안 되네 + + => 그냥 body에 쓰여지는 게 전부임 diff --git a/README.md b/README.md index a40f7fbe3..d21524337 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ +# 일정 +![웹서버개발계획](./%EC%9B%B9%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C%EA%B3%84%ED%9A%8D.png) + +# 이용 가능한 url +서버 실행 후, +- file serving : http://localhost:3490/index.html 접속 +- 난수 리턴 : http://localhost:3490/d20 접속 +- post : curl -D - -X POST -H 'Content-Type: text/plain' -d 'Hello, sample data!' http://localhost:3490/save +- post한 내용이 쓰여진 파일 보기 : http://localhost:3490/posted-file.txt 접속 + +# tips +- vscode 코드 포매팅 : shift + option + f + + # A Simple Web Server in C In this project, we'll finish the implementation of a web server in C. diff --git a/src/cache.c b/src/cache.c index c72975cdd..929ca0462 100644 --- a/src/cache.c +++ b/src/cache.c @@ -3,15 +3,28 @@ #include #include "hashtable.h" #include "cache.h" +#include "llist.h" /** * Allocate a cache entry */ struct cache_entry *alloc_entry(char *path, char *content_type, void *content, int content_length) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + struct cache_entry *ret = malloc(sizeof(struct cache_entry)); + ret->path = path; + ret->content = (char*)content; + ret->content_type = content_type; + ret->content_length = content_length; + + // ret->prev = malloc(sizeof(struct cache_entry)); + // ret->next = malloc(sizeof(struct cache_entry)); + + ret->prev = NULL; + ret->next = NULL; + + ret->created_at = time(NULL); + + return ret; } /** @@ -30,10 +43,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 +62,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 +84,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 +103,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 *ret = malloc(sizeof(struct cache)); + ret->max_size = max_size; + ret->cur_size = 0; + ret->head = NULL; + ret->tail = NULL; + + ret->index = hashtable_create(hashsize, NULL); + + return ret; } void cache_free(struct cache *cache) @@ -102,7 +126,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,22 +142,98 @@ 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! // - /////////////////// + + // 1. Allocate a new cache entry with the passed parameters. + // 2. Insert the entry at the head of the doubly-linked list. + cache->cur_size++; + + printf("in cache_put(), %s %s %s %d\n", path, content_type, (char *)content, content_length); + + struct cache_entry *new_entry = alloc_entry(path, content_type, (char *)content, content_length); + + // head <- new + // new -> head.nxt + // head -> new + // new <- head.nxt + + // struct cache_entry *head = cache->head; //이렇게 head 선언하고 head = new_entry, tail = head 이런식으로 하니까 동작 안 함.. + // struct cache_entry *tail = cache->tail; + + if (cache->head == NULL) + { + cache->head = new_entry; + cache->tail = new_entry; + } + else + { + new_entry->prev = NULL; + new_entry->next = cache->head; + cache->head->prev = new_entry; + cache->head = new_entry; + } + + printf("content = %s \n", (char *)cache->head->content); + + // 3. Store the entry in the hashtable as well, indexed by the entry's path + struct hashtable *ht = cache->index; + hashtable_put(ht, path, new_entry); + + //printf("path = %s\n, retrieved from hashtable = %s\n", path, (char*)((struct cache_entry*)(hashtable_get(ht, path)))->content); + + // 4. Increment the current size of the cache. + // add_entry_count(ht,1); + ht->num_entries += 1; + ht->load = (float)ht->num_entries / ht->size; + + // 5. 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. => ***** how ? synchronized? mutex?******* + if (cache->cur_size > cache->max_size) + { + struct cache_entry *old_tail = dllist_remove_tail(cache); + hashtable_delete(ht, path); + free_entry(old_tail); + + // TODO: 동기화 맞추기 구현해야 함 + } } +// CONFUSE: 이 함수 쓰이나?? /** * Retrieve an entry from the cache */ struct cache_entry *cache_get(struct cache *cache, char *path) { - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // 1. Attempt to find the cache entry pointer by path in the hash table. + struct hashtable *ht = cache->index; + void *data = hashtable_get(ht, path); //리턴이 void * = 만능? + + // 2. If not found, return NULL + if (data == NULL) + { + return NULL; + } + else + { + data = (char *)data; + + struct cache_entry *target = cache->head; + + for (; target != cache->tail && strcmp(target->path, data) != 0; target = target->next) + { // CONFUSE: 맞나?? + } + + // 3. Move the cache entry to the head of the doubly-linked list. + dllist_move_to_head(cache, target); + + // 4. Return the cache entry pointer. + return target; + } } diff --git a/src/cache.h b/src/cache.h index f64c976ad..96aeb2bb2 100644 --- a/src/cache.h +++ b/src/cache.h @@ -1,5 +1,6 @@ #ifndef _WEBCACHE_H_ #define _WEBCACHE_H_ +#include // Individual hash table entry struct cache_entry { @@ -9,6 +10,8 @@ struct cache_entry { void *content; struct cache_entry *prev, *next; // Doubly-linked list + + time_t created_at; }; // A cache diff --git a/src/cache_tests/cache_tests.c b/src/cache_tests/cache_tests.c index 5e245d005..cda8ea937 100644 --- a/src/cache_tests/cache_tests.c +++ b/src/cache_tests/cache_tests.c @@ -57,11 +57,14 @@ char *test_cache_put() // Add in a single entry to the cache cache_put(cache, test_entry_1->path, test_entry_1->content_type, test_entry_1->content, test_entry_1->content_length); + // Check that the cache is handling a single entry as expected mu_assert(cache->cur_size == 1, "Your cache_put function did not correctly increment the cur_size field when adding a new cache entry"); + mu_assert((cache->head != NULL) && (cache->tail != NULL), "head and tail should not null after put one element"); mu_assert(cache->head->prev == NULL && cache->tail->next == NULL, "The head and tail of your cache should have NULL prev and next pointers when a new entry is put in an empty cache"); mu_assert(check_cache_entries(cache->head, test_entry_1) == 0, "Your cache_put function did not put an entry into the head of the empty cache with the expected form"); mu_assert(check_cache_entries(cache->tail, test_entry_1) == 0, "Your cache_put function did not put an entry into the tail of the empty cache with the expected form"); + mu_assert(cache->index !=NULL,"hashtable should not null"); mu_assert(check_cache_entries(hashtable_get(cache->index, "/1"), test_entry_1) == 0, "Your cache_put function did not put the expected entry into the hashtable"); // Add in a second entry to the cache @@ -153,7 +156,7 @@ char *all_tests() mu_run_test(test_cache_create); mu_run_test(test_cache_alloc_entry); mu_run_test(test_cache_put); - mu_run_test(test_cache_get); + //mu_run_test(test_cache_get); return NULL; } diff --git a/src/posted-file.txt b/src/posted-file.txt new file mode 100644 index 000000000..3472a769b --- /dev/null +++ b/src/posted-file.txt @@ -0,0 +1 @@ +Hello, sample data! \ No newline at end of file diff --git a/src/server.c b/src/server.c index 30b878464..48bd88028 100644 --- a/src/server.c +++ b/src/server.c @@ -1,18 +1,24 @@ /** * 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/index.html * curl -D - http://localhost:3490/d20 + * + * + * curl -D - http://localhost:3490/ * 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.) */ @@ -33,59 +39,160 @@ #include "file.h" #include "mime.h" #include "cache.h" +#include "hashtable.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" +int append_str(char *target, char *src, int idx) +{ + for (int j = 0; j < src[j] != NULL; idx++, j++) + { + target[idx] = src[j]; + } + + return idx; +} + /** * Send an HTTP response * * 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! // - /////////////////// + char tmpheader[30]; + for (int i = 0; header[i] != NULL; i++) + { + tmpheader[i] = header[i]; + } + + char *token = strtok(tmpheader, " "); + + char *http_version = token; + token = strtok(NULL, " "); + + char *status = token; + token = strtok(NULL, " "); + + char status_code[20]; + int si = 0; + for (int i = 0; token[i] != NULL; i++) + { + status_code[si++] = token[i]; + } + + if (strcmp(status_code, "NOT") == 0) + { + token = strtok(NULL, " "); + status_code[si++] = ' '; + for (int i = 0; token[i] != NULL; i++) + { + status_code[si++] = token[i]; + } + } + + int idx = 0; + + // http version + idx = append_str(response, http_version, idx); + response[idx++] = ' '; + + // status + idx = append_str(response, status, idx); + response[idx++] = ' '; + + // status code + idx = append_str(response, status_code, idx); + response[idx++] = '\n'; + + // date is optional + time_t now = time(0); + char *time_str = ctime(&now); + time_str[strlen(time_str) - 1] = '\0'; + + idx = append_str(response, "Date: ", idx); + idx = append_str(response, time_str, idx); + response[idx++] = '\n'; + + // connection status + char *connection_status = "Connection: close"; + idx = append_str(response, connection_status, idx); + response[idx++] = '\n'; + + // content(body) length + char content_length_key[20] = "Content-Length: "; + char content_length_str[20]; + sprintf(content_length_str, "%d", content_length); // int -> str + + idx = append_str(response, content_length_key, idx); + idx = append_str(response, content_length_str, idx); + response[idx++] = '\n'; + + // content-type + char content_type_total[50] = "Content-Type: "; + strcat(content_type_total, content_type); + + idx = append_str(response, content_type_total, idx); + // idx += snprintf(response + idx, strlen(content_type_total), "%s\n", content_type_total); // snprintf로 다 바꾸고 싶은데 오류나네.. + + response[idx++] = '\n'; + response[idx++] = '\n'; + + // body + // idx = append_str(response, body, idx); + printf("body = %s\n", body); + idx += snprintf(response + idx, strlen(body) + 1, "%s\n", body); + + // debug response + printf("================ response ===============\n"); + for (int j = 0; j < idx; j++) + { + printf("%c", response[j]); + } + printf("\n"); + printf("=========================================\n"); + + // unsigned int response_length = strlen(header) + strlen(body); + unsigned int response_length = idx; // Send it all! int rv = send(fd, response, response_length, 0); - if (rv < 0) { + if (rv < 0) + { perror("send"); } return rv; } - /** * Send a /d20 endpoint response */ void get_d20(int fd) { // Generate a random number between 1 and 20 inclusive - - /////////////////// - // IMPLEMENT ME! // - /////////////////// - // Use send_response() to send it back as text/plain data + int random = 1 + rand() % 20; + printf("generated random number = %d\n", random); - /////////////////// - // IMPLEMENT ME! // - /////////////////// + char random_number[10]; + sprintf(random_number, "%d", random); // int -> str + + send_response(fd, "HTTP/1.1 200 OK", "text/html", random_number, strlen(random_number)); } /** @@ -94,14 +201,15 @@ 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); @@ -109,6 +217,8 @@ void resp_404(int fd) mime_type = mime_type_get(filepath); + printf("in resp_404(), filepath = %s\n", filepath); + send_response(fd, "HTTP/1.1 404 NOT FOUND", mime_type, filedata->data, filedata->size); file_free(filedata); @@ -119,14 +229,40 @@ 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; + + // Fetch the {filename}.html file + snprintf(filepath, sizeof filepath, "%s", SERVER_FILES); + strcat(filepath, request_path); + + printf("in get_file(), filepath = %s\n", filepath); + + filedata = file_load(filepath); + + if (filedata == NULL) + { + // TODO: make this non-fatal + // fprintf(stderr, "cannot find system %s file\n", filepath); + resp_404(fd); + return; + // exit(3); + } + + mime_type = mime_type_get(filepath); + + // cached list에 추가 + cache_put(cache, request_path, mime_type, filedata->data, filedata->size); + + send_response(fd, "HTTP/1.1 200 OK", mime_type, filedata->data, filedata->size); + + 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). */ @@ -135,6 +271,35 @@ char *find_start_of_body(char *header) /////////////////// // IMPLEMENT ME! // (Stretch) /////////////////// + + header = strtok(header, "\n"); + char *token; + for (;;) + { + token = strtok(NULL, "\n"); + if (token == NULL) + { + break; + } + if (strcmp(token, "\r") == 0) + { + printf("found\n"); + token = strtok(NULL, "\n"); + break; + } + printf("token = %s\n", token); + } + + return token; +} + +int post_save(char *body) +{ + FILE *fp = fopen("./serverfiles/posted-file.txt", "w"); + fputs(body, fp); + fclose(fp); + + return 0; } /** @@ -148,25 +313,82 @@ 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; } + printf("============= request ==============\n"); + printf(request); + printf("====================================\n"); - /////////////////// - // IMPLEMENT ME! // - /////////////////// + char request_type[30]; + char path[100]; + + // Read the first two components of the first line of the request + sscanf(request, "%s %s", request_type, path); + + printf("%s %s\n", request_type, path); - // Read the first two components of the first line of the request - // If GET, handle the get endpoints + if (strcmp(request_type, "GET") == 0) + { + if (strcmp(path, "/d20") == 0) + { + get_d20(fd); + } + else + { + struct hashtable *ht = cache->index; + // struct cache_entry *cached_data = (struct cache_entry *)hashtable_get(ht, path); // CONFUSE: hashtable_get 하면 LRU 기능 동작 안하지 않나? cached_get으로 가져와야 하는듯? + struct cache_entry *cached_data = cache_get(cache, path); + + if (cached_data == NULL) + { + get_file(fd, cache, path); + } + else + { + char *cached_content = (char *)(cached_data->content); + + time_t created_at = cached_data->created_at; + time_t now = time(NULL); + if (now - created_at > 60) + { + printf("update cached data\n"); + get_file(fd, cache, path); + } + else + { + printf("just use cached data\n"); + // CONFUSE: 캐시 적용을 어디서 해야 할지.. + // CONFUSE: mime_type 그냥 text/html로 고정했는데 수정해야 하나? + send_response(fd, "HTTP/1.1 200 OK", "text/html", cached_content, strlen(cached_content)); + } + } + } + } + else if (strcmp(request_type, "POST") == 0 && strcmp(path, "/save") == 0) + { + + char *body = find_start_of_body(request); + printf("body = %s\n", body); - // Check if it's /d20 and handle that special case - // Otherwise serve the requested file by calling get_file() + int ret = post_save(body); + if (ret == 0) + { + printf("post success\n"); + char *response = "{\"status\":\"ok\"}"; - // (Stretch) If POST, handle the post request + send_response(fd, "HTTP/1.1 201 CREATED", "application/json", response, strlen(response)); + } + else + { + printf("post failed\n"); + } + } } /** @@ -174,7 +396,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 +405,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 +416,26 @@ int main(void) // This is the main loop that accepts incoming connections and // responds to the request. 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 +448,3 @@ int main(void) return 0; } - diff --git a/src/serverfiles/404.html b/src/serverfiles/404.html index 5f362abf9..4bdd16b76 100644 --- a/src/serverfiles/404.html +++ b/src/serverfiles/404.html @@ -8,6 +8,6 @@

404 Not Found

- These are not the droids you're looking for. + no file found for requested url \ No newline at end of file diff --git a/src/serverfiles/index.html b/src/serverfiles/index.html new file mode 100644 index 000000000..4f8d36aa7 --- /dev/null +++ b/src/serverfiles/index.html @@ -0,0 +1,13 @@ + + + + + index file + + +

index file!!

+ I want to serve this file.
+ changed
+ changed2
+ + \ No newline at end of file diff --git "a/\354\233\271\354\204\234\353\262\204\352\260\234\353\260\234\352\263\204\355\232\215.png" "b/\354\233\271\354\204\234\353\262\204\352\260\234\353\260\234\352\263\204\355\232\215.png" new file mode 100644 index 000000000..b66ca1260 Binary files /dev/null and "b/\354\233\271\354\204\234\353\262\204\352\260\234\353\260\234\352\263\204\355\232\215.png" differ