From: Andrew Gundersen Date: Mon, 2 May 2022 13:00:44 +0000 (-0500) Subject: init commit X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;p=http-server init commit --- 9904bd221296b90fd7d255660620a556ed2c3f23 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1530978 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.o \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aca5503 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Addon Libraries to C Programming Language \ No newline at end of file diff --git a/hashtable/Makefile b/hashtable/Makefile new file mode 100644 index 0000000..c4d0f72 --- /dev/null +++ b/hashtable/Makefile @@ -0,0 +1,21 @@ +CC=gcc +CFLAGS=-Wall -Wextra + +OBJS=test.o hashtable.o llist.o + +all: test + +test: $(OBJS) + gcc -o $@ $^ + +test.o: test.c http.h + +hashtable.o: hashtable.c hashtable.h + +llist.o: llist.c llist.h + +clean: + rm -f $(OBJS) + rm -f test + +.PHONY: all, clean diff --git a/hashtable/hashtable.c b/hashtable/hashtable.c new file mode 100644 index 0000000..51eb5c1 --- /dev/null +++ b/hashtable/hashtable.c @@ -0,0 +1,153 @@ +/** + * hashtable.c - A basic hashtable implementation + * + */ + +#include +#include + +#include "llist.h" +#include "hashtable.h" + +#define SIZE 128 + +typedef struct +{ + void* key; + void* data; +} +HTEntry; + +/** + * Simple hash function used + */ +int hash(char* key, int ht_size) +{ + int h = 0; + + // basic modulo hash + for (size_t i = 0; i < strlen(key); i++) + { + h = (31 * h + key[i]) % ht_size; + } + + return h; +} + +/** + * Compare function for searching a bucket + */ +int compare(void* data, void* arg) +{ + char* key = arg; + HTEntry* entry = data; + return strcmp(entry->key, key); +} + +/** + * Delete HT entry from bucket + */ +int delete_entry(void* data, void* arg) +{ + (void) arg; + HTEntry* entry = data; + free(entry); + return 0; +} + +/** + * Create a new hashtable + */ +HT* ht_create(void) +{ + HT* ht = malloc(sizeof(HT)); + + ht->size = SIZE; + ht->entries = 0; + ht->store = malloc(ht->size * sizeof(LL*)); + + for (int i = 0; i < ht->size; i++) + { + ht->store[i] = ll_create(); + } + + return ht; +} + +/** + * Add a new key value pair to a hashtable + */ +int ht_put(HT* ht, char* key, void* data) +{ + int addr = hash(key, ht->size); + + LL* bucket = ht->store[addr]; + + HTEntry* entry = malloc(sizeof(HTEntry)); + entry->key = strdup(key); + entry->data = data; + + ll_push(bucket, entry); + + ht->entries++; + + return 0; +} + +/** + * Get a value from a hashtable with a key + */ +void* ht_get(HT* ht, char* key) +{ + int addr = hash(key, ht->size); + + LL* bucket = ht->store[addr]; + + HTEntry* entry = ll_search(bucket, compare, (void*) key); + + if (entry == NULL) + { + return NULL; + } + + return entry->data; +} + +/** + * Delete a key value pair from a hashtable + */ +void* ht_delete(HT* ht, char* key) +{ + int addr = hash(key, ht->size); + + LL* bucket = ht->store[addr]; + + HTEntry* entry = ll_delete(bucket, compare, key); + + if (entry == NULL) + { + return NULL; + } + + ht->entries--; + + return entry->data; +} + + +/** + * Destroy/free an entire hashtable + */ +int ht_free(HT* ht) +{ + for (int i = 0; i < ht->size; i++) + { + LL* bucket = ht->store[i]; + ll_loop(bucket, delete_entry, NULL); + ll_free(bucket); + } + + free(ht); + + return 0; +} diff --git a/hashtable/hashtable.h b/hashtable/hashtable.h new file mode 100644 index 0000000..b974cb8 --- /dev/null +++ b/hashtable/hashtable.h @@ -0,0 +1,24 @@ +#ifndef _HASHTABLE_H_ +#define _HASHTABLE_H_ + +#include "llist.h" + +typedef struct +{ + int size; + int entries; + LL** store; +} +HT; + +HT* ht_create(void); + +int ht_put(HT* ht, char* key, void* data); + +void* ht_get(HT* ht, char* key); + +void* ht_delete(HT* ht, char* key); + +int ht_free(HT* ht); + +#endif diff --git a/hashtable/test.c b/hashtable/test.c new file mode 100644 index 0000000..5b3636d --- /dev/null +++ b/hashtable/test.c @@ -0,0 +1,34 @@ +#include +#include + +#include "hashtable.h" + +int main(void) +{ + puts("Creating new hashtable..."); + + HT* ht = ht_create(); + + char* login_data = "This is the login data!"; + char* register_data = "This is the register data!"; + + puts("Putting in some data..."); + + ht_put(ht, "/login", login_data); + ht_put(ht, "/register", register_data); + + puts("Getting values and asserting they are correct..."); + + assert(login_data == ht_get(ht, "/login")); + assert(register_data == ht_get(ht, "/register")); + + puts("Checking that delete works..."); + + ht_delete(ht, "/login"); + + assert(ht_get(ht, "/login") == NULL); + + puts("Tests complete!"); + + return 0; +} \ No newline at end of file diff --git a/http/Makefile b/http/Makefile new file mode 100644 index 0000000..a8303f5 --- /dev/null +++ b/http/Makefile @@ -0,0 +1,25 @@ +CC=gcc +CFLAGS=-Wall -Wextra + +OBJS=test.o http.o net.o hashtable.o llist.o + +all: test + +test: $(OBJS) + gcc -o $@ $^ + +test.o: test.c http.h + +http.o: http.c http.h + +net.o: net.c net.h + +hashtable.o: hashtable.c hashtable.h + +llist.o: llist.c llist.h + +clean: + rm -f $(OBJS) + rm -f test + +.PHONY: all, clean diff --git a/http/http.c b/http/http.c new file mode 100644 index 0000000..007ca2a --- /dev/null +++ b/http/http.c @@ -0,0 +1,137 @@ +/** + * http.c - Basic HTTP framework implentation + * + * hashtable.c for routing + * listen.c for socket creation + * + */ + +#include +#include +#include +#include +#include + +#include "hashtable.h" +#include "listen.h" +#include "http.h" + +#define BUF_SIZE 65536 +#define HTTP_RES "HTTP/1.1 %d MESSAGE\n"\ +"Connection: close\n"\ +"Content-Length: %d\n"\ +"Content-Type: %s\n"\ +"\n"\ +"%s" + +HT* routes; + +void use(char* path, Handler handler) +{ + if (routes == NULL) + { + routes = ht_create(); + } + + // TODO: handle for key exist + ht_put(routes, path, (void*) handler); +} + +void send_response(int client, Res* res) +{ + char res_raw[BUF_SIZE]; + + sprintf( res_raw, + HTTP_RES, + res->status, + res->content_length, + res->content_type, + res->body) ; + + int num_sent = send(client, res_raw, strlen(res_raw), 0); + + if (num_sent < 0) + { + perror("send"); + } +} + +void fallback_handler(Res* res) +{ + char* content = "Bad request\n"; + + res->status = 400; + res->content_length = strlen(content); + res->content_type = "text/html"; + res->body = content; +} + +void parse_req_raw(char* raw, Req* req) +{ + printf("%s\n", raw); + + sscanf(raw, "%s %s", req->type, req->path); +} + +void handle_request(int client) +{ + char req_raw[BUF_SIZE]; + Req req; + Res res; + + int num_recvd = recv(client, req_raw, BUF_SIZE - 1, 0); + + if (num_recvd < 0) + { + perror("recv"); + return; + } + + parse_req_raw(req_raw, &req); // TODO: handle for parse. + + printf("Request: %s, %s\n", req.type, req.path); + + Handler handler = ht_get(routes, req.path); + + if (handler == NULL) + { + fallback_handler(&res); + } + else + { + handler(&req, &res); + } + + send_response(client, &res); +} + +int http_start(char* port) +{ + int client, server; + + if ((server = init_server_socket(port)) < 0) + { + puts("Error when establishing the server."); + exit(1); + } + + printf("Waiting for connections on port %s...\n", port); + + while(1) + { + client = accept(server, (struct sockaddr*) NULL, NULL); + + if (client == -1) + { + perror("accept"); + continue; + } + + printf("Client connected.\n"); + + handle_request(client); + close(client); + } + + return 0; +} diff --git a/http/http.h b/http/http.h new file mode 100644 index 0000000..db8b1b6 --- /dev/null +++ b/http/http.h @@ -0,0 +1,31 @@ +#ifndef _HTTP_H_ +#define _HTTP_H_ + +#define PORT "3000" + +typedef struct +{ + char type[100]; + char path[200]; +} +Req; + +typedef struct +{ + int status; + char* content_type; + int content_length; + char* body; +} +Res; + +/* User defined handlers must be this shape */ +typedef void (*Handler)(Req* req, Res* res); + +/* Add an api endpoint */ +void use(char* path, Handler handler); + +/* Start the server */ +int http_start(char* port); + +#endif diff --git a/http/listen.c b/http/listen.c new file mode 100644 index 0000000..91bc90e --- /dev/null +++ b/http/listen.c @@ -0,0 +1,62 @@ +/** + * listen.c - Create and bind a server socket to a port + * + */ + +#include +#include +#include +#include + +#include "listen.h" + +#define QUEUE 8 + + +int init_server_socket(char* port) +{ + int fd, yes = 1; + struct addrinfo hints, *servinfo, *p; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + getaddrinfo(NULL, port, &hints, &servinfo); + + for (p = servinfo; p != NULL; p = p->ai_next) + { + if ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) + { + continue; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { + close(fd); + freeaddrinfo(servinfo); + return -1; + } + + if (bind(fd, p->ai_addr, p->ai_addrlen) == -1) + { + close(fd); + continue; + } + + break; + } + + freeaddrinfo(servinfo); + + if (p == NULL) { return -1; } + + if (listen(fd, QUEUE) == -1) + { + close(fd); + return -1; + } + + return fd; +} \ No newline at end of file diff --git a/http/listen.h b/http/listen.h new file mode 100644 index 0000000..c65a0a6 --- /dev/null +++ b/http/listen.h @@ -0,0 +1,6 @@ +#ifndef _LISTEN_H_ +#define _LISTEN_H_ + +int init_server_socket(char *port); + +#endif \ No newline at end of file diff --git a/http/main.c b/http/main.c new file mode 100644 index 0000000..9cc5e50 --- /dev/null +++ b/http/main.c @@ -0,0 +1,48 @@ +/** + * main.c - HTTP server that uses http.c framework + * + * curl http://localhost:3000/api/login + * + */ + +#include +#include + +#include "http.h" + +void login(Req* req, Res* res) +{ + (void) req; + + char* content = "Login works!\n"; + + res->status = 200; + res->content_length = strlen(content); + res->content_type = "text/html"; + res->body = content; +} + +void signup(Req* req, Res* res) +{ + (void) req; + + char* content = "Sign Up works!\n"; + + res->status = 200; + res->content_length = strlen(content); + res->content_type = "text/html"; + res->body = content; +} + +/** + * Register the endpoints and start the server. + */ +int main(void) +{ + puts("Registering endpoints"); + + use("/signup", &signup); + use("/login", &login); + + http_start(PORT); +} diff --git a/llist/Makefile b/llist/Makefile new file mode 100644 index 0000000..1a1b704 --- /dev/null +++ b/llist/Makefile @@ -0,0 +1,19 @@ +CC=gcc +CFLAGS=-Wall -Wextra + +OBJS=test.o llist.o + +all: test + +test: $(OBJS) + gcc -o $@ $^ + +test.o: test.c + +llist.o: llist.c llist.h + +clean: + rm -f $(OBJS) + rm -f test + +.PHONY: all, clean diff --git a/llist/llist.c b/llist/llist.c new file mode 100644 index 0000000..5f3921c --- /dev/null +++ b/llist/llist.c @@ -0,0 +1,174 @@ +/** + * llist.c - a basic linked list implementation. + * + * Used for the hashtable. + * + */ + +#include +#include + +#include "llist.h" + +/** + * Node in the linked list + */ +typedef struct LLNodeT +{ + void* data; + struct LLNodeT* next; +} +LLNode; + +/** + * Create a new linked list + */ +LL* ll_create(void) +{ + LL* ll = malloc(sizeof(LL)); + ll->entries = 0; + return ll; +} + +/** + * Add item to beginning of a linked list + */ +int ll_push(LL* ll, void* data) +{ + LLNode* n = malloc(sizeof(*n)); + + n->data = data; + + // Reassign the head + n->next = ll->head; + ll->head = n; + + ll->entries++; + + return 0; +} + +/** + * Add item to end of a linked list + */ +int ll_append(LL* ll, void* data) +{ + LLNode* tail = ll->head; + + // Just push if there are no nodes yet + if (tail == NULL) + { + return ll_push(ll, data); + } + + // Travserse list to find tail + while (tail->next != NULL) + { + tail = tail->next; + } + + // Create new node and assign it as tail + LLNode* n = malloc(sizeof(LLNode)); + n->data = data; + tail->next = n; + + ll->entries++; + + return 0; +} + +/** + * Get an item from the list via cb function + */ +void* ll_search(LL* ll, LLCb cb, void* arg) +{ + LLNode* n = ll->head; + + // Keep going until tail is reached + while (n != NULL) + { + if (cb(n->data, arg) == 0) + { + break; + } + + n = n->next; + } + + if (n == NULL) + { + return NULL; + } + + return n->data; +} + +/** + * Find and delete an entry from a linked list + */ +void* ll_delete(LL* ll, LLCb cb, void* arg) +{ + LLNode* n = ll->head, *prev = NULL; + + while (n != NULL) + { + if (cb(n->data, arg) == 0) + { + void* data = n->data; + + // We are at the head + if (prev == NULL) + { + ll->head = n->next; + free(n); + } + else + { + prev->next = n->next; + free(n); + } + + return data; + } + + prev = n; + n = n->next; + } + + return NULL; +} + +/** + * Apply a function to every node in a linked list + */ +int ll_loop(LL* ll, LLCb cb, void* arg) +{ + LLNode* n = ll->head, *next; + + while (n != NULL) + { + next = n->next; + cb(n->data, arg); + n = next; + } + + return 0; +} + +/** + * Free a linked list + */ +void ll_free(LL* ll) +{ + LLNode* n = ll->head, *next; + + while (n != NULL) + { + next = n->next; + free(n); + n = next; + } + + free(ll); +} + diff --git a/llist/llist.h b/llist/llist.h new file mode 100644 index 0000000..e063e13 --- /dev/null +++ b/llist/llist.h @@ -0,0 +1,27 @@ +#ifndef _LLIST_H_ +#define _LLIST_H_ + +typedef struct +{ + struct LLNodeT* head; + int entries; +} +LL; + +typedef int (*LLCb)(void*, void*); + +LL* ll_create(void); + +int ll_push(LL* ll, void* data); + +int ll_append(LL* ll, void* data); + +void* ll_search(LL* ll, LLCb cb, void* arg); + +void* ll_delete(LL* ll, LLCb cb, void* arg); + +int ll_loop(LL* ll, LLCb cb, void* arg); + +void ll_free(LL* ll); + +#endif \ No newline at end of file diff --git a/llist/test.c b/llist/test.c new file mode 100644 index 0000000..8ee3fe3 --- /dev/null +++ b/llist/test.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#include "llist.h" + +typedef struct +{ + char* name; + char* team; + int age; +} +Player; + +Player* create_player(char* name, char* team, int age) +{ + Player* p = malloc(sizeof(*p)); + p->team = strdup(team); + p->name = strdup(name); + p->age = age; + return p; +} + +int find_player(void* data, void* arg) +{ + Player* player = data; + return strcmp(player->name, (char*) arg); +} + +int change_team(void* data, void* arg) +{ + Player* player = data; + player->team = (char*) arg; + return 0; +} + +int main(void) +{ + puts("Creating new linked list..."); + + LL* ll = ll_create(); + + puts("Pushing some data..."); + + Player* lebron = create_player("Lebron James", "Lakers", 37); + Player* giannis = create_player("Giannis Antetokounmpo", "Bucks", 27); + + ll_push(ll, lebron); + ll_push(ll, giannis); + + puts("Finding the data that was pushed..."); + + assert(ll_search(ll, find_player, "Lebron James") == lebron); + + puts("Appending some data..."); + + Player* kd = create_player("Kevin Durant", "Nets", 33); + + ll_append(ll, kd); + + puts("Finding the data that was appended..."); + + assert(ll_search(ll, find_player, "Kevin Durant") == kd); + + puts("Looping over linked list and applying change..."); + + ll_loop(ll, change_team, "Bucks"); + + Player* kd_ref = ll_search(ll, find_player, "Kevin Durant"); + + assert(strcmp(kd_ref->team, "Bucks") == 0); + + puts("Deleting data from linked list..."); + + ll_delete(ll, find_player, "Lebron James"); + + assert(ll_search(ll, find_player, "Lebron James") == NULL); + + puts("Tests complete!"); + + return 0; +} \ No newline at end of file