diff options
| Commit | 74630b4bfe1aa887ef57ee57b22241d030b38d76 (patch) | |
|---|---|---|
| Author | 2025-11-15 21:37:37 +0200 | |
| Committer | 2025-11-15 21:37:37 +0200 | |
| Files | 3d39c585df1551d69fdb83980c010ec4b579f211 | |
| tergiv-74630b4bfe1aa887ef57ee57b22241d030b38d76.tar tergiv-74630b4bfe1aa887ef57ee57b22241d030b38d76.tar.gz tergiv-74630b4bfe1aa887ef57ee57b22241d030b38d76.zip | ||
| Parent | 3bca23eaaa49fa01f60ecbc5e0a329d29f088506 (diff) | |
feat!: Initial web server base implementation
12 files changed, 459 insertions, 0 deletions
| -rw-r--r-- | src/http/callback_http.c | 136 | ||||
| -rw-r--r-- | src/http/callback_http.h | 38 | ||||
| -rw-r--r-- | src/http/http_headers.h | 17 | ||||
| -rw-r--r-- | src/http/request_method.h | 30 | ||||
| -rw-r--r-- | src/http/response_code.h | 18 | ||||
| -rw-r--r-- | src/main.c | 34 | ||||
| -rw-r--r-- | src/sockets/callback_echo.c | 6 | ||||
| -rw-r--r-- | src/sockets/callback_echo.h | 8 | ||||
| -rw-r--r-- | src/sockets/socket_server.c | 99 | ||||
| -rw-r--r-- | src/sockets/socket_server.h | 31 | ||||
| -rw-r--r-- | src/utils/strings.c | 32 | ||||
| -rw-r--r-- | src/utils/strings.h | 10 |
diff --git a/src/http/callback_http.c b/src/http/callback_http.c new file mode 100644 index 0000000..5b2b944 --- /dev/null +++ b/src/http/callback_http.c @@ -0,0 +1,136 @@ +#include "callback_http.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "../utils/strings.h" +#include "http_headers.h" +#include "request_method.h" +#include "response_code.h" + +static const char* SEPS = " \n\r"; +#define return_bad_request() { \ + args->output = "HTTP/1.1 400 Bad Request"; \ + args->output_size = 24; \ + return; \ + } + +static void append_header(enum HttpHeader header, const char* value, const char** parts_array, size_t *ind); + +void callback_http(callback_args* args) { + if (args->extra == NULL) { + printf("No HTTP callbacks were set!"); + return; + } + + http_callbacks *callbacks = args->extra; + + /* Parse input request */ + + client_message in_message = { + .method = RM_UNKNOWN, + .url = NULL, + .version = NULL, + .headers = { 0 }, + }; + + /** Request method **/ + + char* word = strtok(args->input, SEPS); + if (word == NULL) return_bad_request() + + for (enum RequestMethod rm = RM_GET; rm < RM_UNKNOWN; rm++) { + if (streq(REQUEST_METHOD_NAME[rm], word)) { + in_message.method = rm; + break; + } + } + + if (in_message.method == RM_UNKNOWN) + return_bad_request() + + /** URL **/ + + word = strtok(NULL, SEPS); + if (word == NULL) return_bad_request() + + in_message.url = word; + + /** HTTP version **/ + + word = strtok(NULL, SEPS); + if (word == NULL || !streq("HTTP/1.1", word)) + return_bad_request() + + in_message.version = word; + + /** Headers **/ + + word = strtok(NULL, ":\n\r"); + + while(word != NULL) { + enum HttpHeader he = 0; + + while (he < HE_LENGTH) { + if (streq(HTTP_HEADER_NAME[he], word)) + break; + ++he; + } + word = strtok(NULL, "\n\r"); + + if (word == NULL) break; + + if (he < HE_LENGTH) { + while (*word == ' ') ++word; + in_message.headers[he] = word; + } + + word = strtok(NULL, ":\n\r"); + } + + /* Respond to request */ + + response_message out_message = { + .response_code = RC_500_INTERNAL_SERVER_ERRROR, + + .headers = { 0 }, + + .body = NULL, + .body_size = 0, + .free_body = 0, + }; + + if (in_message.method < RM_UNKNOWN) + callbacks->all_callbacks[in_message.method](&in_message, &out_message); + else + printf("Unsupported method!"); + + const char* response[1024] = { 0 }; + size_t rind = 0; + + response[rind++] = "HTTP/1.1 "; + response[rind++] = RESPONSE_CODE_NAME[out_message.response_code]; + response[rind++] = "\n"; + + for (enum HttpHeader he = 0; he < HE_LENGTH; he++) { + if (out_message.headers[he] != NULL) + append_header(he, out_message.headers[he], response, &rind); + } + + response[rind++] = "\n"; + + if (out_message.body != NULL && out_message.body_size > 0) + response[rind++] = out_message.body; + + args->output = concat_array(response, rind, &args->output_size); + args->free_output = 1; + + if (out_message.free_body) + free(out_message.body); +} + +static void append_header(enum HttpHeader header, const char* value, const char** parts_array, size_t *ind) { + parts_array[(*ind)++] = HTTP_HEADER_NAME[header]; + parts_array[(*ind)++] = ": "; + parts_array[(*ind)++] = value; + parts_array[(*ind)++] = "\n"; +} diff --git a/src/http/callback_http.h b/src/http/callback_http.h new file mode 100644 index 0000000..5f38c86 --- /dev/null +++ b/src/http/callback_http.h @@ -0,0 +1,38 @@ +#ifndef __TERGIV_CALLBACK_HTTP +#define __TERGIV_CALLBACK_HTTP + +#include "../sockets/socket_server.h" +#include "request_method.h" +#include "response_code.h" +#include "http_headers.h" + +void callback_http(callback_args* args); + +typedef struct client_message { + enum RequestMethod method; + char* url; + char* version; + char* headers[HE_LENGTH]; +} client_message; + +typedef struct response_message { + enum ResponseCode response_code; + + char* headers[HE_LENGTH]; + + char* body; + size_t body_size; + int free_body; +} response_message; + +typedef struct http_callbacks { + union { + struct { + void (*get_callback)(client_message* in_message, response_message* out_message); + }; + void (*all_callbacks[RM_UNKNOWN])(client_message* in_message, response_message* out_message); + }; + +} http_callbacks; + +#endif /* __TERGIV_CALLBACK_HTTP */ diff --git a/src/http/http_headers.h b/src/http/http_headers.h new file mode 100644 index 0000000..0e83dfd --- /dev/null +++ b/src/http/http_headers.h @@ -0,0 +1,17 @@ +#ifndef __TERGIV_HTTP_HEADERS +#define __TERGIV_HTTP_HEADERS + +enum HttpHeader { + HE_Host = 0, + HE_Content_Length, + HE_Content_Type, + HE_LENGTH, +}; + +static const char* HTTP_HEADER_NAME[] = { + "Host", + "Content-Length", + "Content-Type", +}; + +#endif /* __TERGIV_HTTP_HEADERS */ diff --git a/src/http/request_method.h b/src/http/request_method.h new file mode 100644 index 0000000..32ec13e --- /dev/null +++ b/src/http/request_method.h @@ -0,0 +1,30 @@ +#ifndef __TERGIV_REQUEST_METHOD +#define __TERGIV_REQUEST_METHOD + +enum RequestMethod { + RM_GET = 0, + RM_HEAD, + RM_OPTIONS, + RM_TRACE, + RM_PUT, + RM_DELETE, + RM_POST, + RM_PATCH, + RM_CONNECT, + RM_UNKNOWN +}; + +static const char* REQUEST_METHOD_NAME[] = { + "GET", + "HEAD", + "OPTIONS", + "TRACE", + "PUT", + "DELETE", + "POST", + "PATCH", + "CONNECT", + 0 +}; + +#endif /* __TERGIV_REQUEST_METHOD */ diff --git a/src/http/response_code.h b/src/http/response_code.h new file mode 100644 index 0000000..f5cd191 --- /dev/null +++ b/src/http/response_code.h @@ -0,0 +1,18 @@ +#ifndef __TERGIV_RESPONSE_CODE +#define __TERGIV_RESPONSE_CODE + +enum ResponseCode { + RC_200_OK, + RC_403_FORBIDDEN, + RC_404_NOT_FOUND, + RC_500_INTERNAL_SERVER_ERRROR, + RC_LENGTH +}; + +static const char* RESPONSE_CODE_NAME[] = { + "200 OK", + "403 FORBIDDEN", + "404 NOT_FOUND", +}; + +#endif /* __TERGIV_RESPONSE_CODE */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..cdd24cf --- /dev/null +++ b/src/main.c @@ -0,0 +1,34 @@ +#include "http/callback_http.h" +#include "http/http_headers.h" +#include "http/response_code.h" +#include "sockets/socket_server.h" +#include "utils/strings.h" +#include <stdio.h> + +void test(client_message* in_msg, response_message* out_msg) { + if (streq("/test", in_msg->url)) { + out_msg->response_code = RC_200_OK; + + out_msg->body = "Hello World!"; + out_msg->body_size = strlen(out_msg->body); + + out_msg->headers[HE_Content_Length] = "12"; + out_msg->headers[HE_Content_Type] = "text/plain"; + } +} + +int main() { + socket_server* server = new_socket_server_single_req(8081, INADDR_LOOPBACK); + + http_callbacks http_c = { + .get_callback = test, + }; + server->callback = callback_http; + server->callback_args = &http_c; + server->callback_argc = 1; + + server->start(server); + server->destroy(server); + + return 0; +} diff --git a/src/sockets/callback_echo.c b/src/sockets/callback_echo.c new file mode 100644 index 0000000..03b4868 --- /dev/null +++ b/src/sockets/callback_echo.c @@ -0,0 +1,6 @@ +#include "callback_echo.h" + +void callback_echo(callback_args* args) { + args->output = (void*)args->input; + args->output_size = args->input_size; +} diff --git a/src/sockets/callback_echo.h b/src/sockets/callback_echo.h new file mode 100644 index 0000000..dc2c04f --- /dev/null +++ b/src/sockets/callback_echo.h @@ -0,0 +1,8 @@ +#ifndef __TERGIV_CALLBACK_ECHO +#define __TERGIV_CALLBACK_ECHO + +#include "socket_server.h" + +void callback_echo(callback_args* args); + +#endif /* __TERGIV_CALLBACK_ECHO */ diff --git a/src/sockets/socket_server.c b/src/sockets/socket_server.c new file mode 100644 index 0000000..5280341 --- /dev/null +++ b/src/sockets/socket_server.c @@ -0,0 +1,99 @@ +#include "socket_server.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static socket_server* __new_socket_server(uint16_t port, uint32_t incoming_ip) { + socket_server* srv = malloc(sizeof(socket_server)); + + srv->socket = socket(AF_INET, SOCK_STREAM, 0); + + struct sockaddr_in addr = { + .sin_family = AF_INET, // Address Family Internet + .sin_port = htons(port), // Host to network short + .sin_addr.s_addr = htonl(incoming_ip), // Host to network long + }; + + while (bind(srv->socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + perror("Socket bind"); + sleep(5); + } + + return srv; +} + +/*** Single Request Server ***/ + +static int start_single_req(socket_server* this); +static void destroy_single_req(socket_server* this); + +socket_server* new_socket_server_single_req(uint16_t port, uint32_t incoming_ip) { + socket_server* res = __new_socket_server(port, incoming_ip); + res->start = start_single_req; + res->destroy = destroy_single_req; + return res; +} + +static int ends_on_two_newlines(char str[2]) { + return (str[0] == '\n' || str[0] == '\r') && (str[1] == '\n' || str[1] == '\r'); +} + +static int start_single_req(socket_server* this) { + if (this == NULL) return -1; + + listen(this->socket, 1); + + struct sockaddr_in client = { + .sin_family = AF_INET, + .sin_port = 0, + .sin_addr.s_addr = 0, + }; + socklen_t client_size = sizeof(client); + + int client_socket = accept(this->socket, (struct sockaddr*)&client, &client_size); + + char input[1024] = { 0 }; + size_t input_size = 0; + + char line[256] = { 0 }; + size_t line_size = 0; + do { + input_size += line_size = recv(client_socket, line, 255, 0); + line[line_size] = '\0'; + strcat(input, line); + } while (input_size > 0 && !ends_on_two_newlines(input + input_size - 2)); + + callback_args ca_args = { + .input = input, + .input_size = input_size, + + .output = NULL, + .output_size = 0, + .free_output = 0, + + .extra = this->callback_args, + .extra_size = this->callback_argc, + }; + + if (this->callback != NULL) + this->callback(&ca_args); + + if (ca_args.output != NULL && ca_args.output_size > 0) + send(client_socket, ca_args.output, ca_args.output_size, 0); + + if (ca_args.free_output) + free(ca_args.output); + + close(client_socket); + + return 0; +} + +static void destroy_single_req(socket_server* this) { + if (this == NULL) return; + + close(this->socket); + free(this); + this = NULL; +} diff --git a/src/sockets/socket_server.h b/src/sockets/socket_server.h new file mode 100644 index 0000000..c74c4fd --- /dev/null +++ b/src/sockets/socket_server.h @@ -0,0 +1,31 @@ +#ifndef __TERGIV_SOCKET_SERVER +#define __TERGIV_SOCKET_SERVER + +#include <arpa/inet.h> + +typedef struct callback_args { + void* input; + size_t input_size; + + void* output; + size_t output_size; + int free_output; + + void* extra; + size_t extra_size; +} callback_args; + +typedef struct socket_server { + int socket; + + int (*start)(struct socket_server* self); + void (*destroy)(struct socket_server* self); + + void (*callback)(callback_args* args); + void* callback_args; + size_t callback_argc; +} socket_server; + +socket_server* new_socket_server_single_req(uint16_t port, uint32_t incoming_ip); + +#endif /* __TERGIV_SOCKET_SERVER */ diff --git a/src/utils/strings.c b/src/utils/strings.c new file mode 100644 index 0000000..02b33f1 --- /dev/null +++ b/src/utils/strings.c @@ -0,0 +1,32 @@ +#include "strings.h" +#include <stdlib.h> + +int strprefix(const char* prefix, const char* str) { + return strncmp(prefix, str, strlen(prefix)) == 0; +} + +int streq(const char* a, const char* b) { + return strncmp(a, b, strlen(a)) == 0; +} + +char* concat_array(const char** array, size_t size, size_t *output_size) { + if (array == NULL || size == 0) return NULL; + + size_t total_size = 0; + for (size_t i = 0; i < size; i++) + total_size += strlen(array[i]); + + if (total_size == 0) return NULL; + + char* result = malloc(total_size + 1); + size_t current_index = 0; + + for (size_t i = 0; i < size; i++) { + size_t curr_len = strlen(array[i]); + strncpy(result + current_index, array[i], curr_len); + current_index += curr_len; + } + + if (output_size != NULL) *output_size = total_size; + return result; +} diff --git a/src/utils/strings.h b/src/utils/strings.h new file mode 100644 index 0000000..afb01b8 --- /dev/null +++ b/src/utils/strings.h @@ -0,0 +1,10 @@ +#ifndef __TERGIV_STINGS_UTILS +#define __TERGIV_STINGS_UTILS + +#include <string.h> + +int strprefix(const char* prefix, const char* str); +int streq(const char* a, const char* b); +char* concat_array(const char** array, size_t size, size_t *output_size); + +#endif /* __TERGIV_STINGS_UTILS */ |
