Summary Files Commits Branches @commit @diff @stats
diff options
context:
space:
mode:
Commit74630b4bfe1aa887ef57ee57b22241d030b38d76 (patch)
Author
 Syndamia <kamen@syndamia.com>
2025-11-15 21:37:37 +0200
Committer
 Syndamia <kamen@syndamia.com>
2025-11-15 21:37:37 +0200
Files3d39c585df1551d69fdb83980c010ec4b579f211
tergiv-74630b4bfe1aa887ef57ee57b22241d030b38d76.tar
tergiv-74630b4bfe1aa887ef57ee57b22241d030b38d76.tar.gz
tergiv-74630b4bfe1aa887ef57ee57b22241d030b38d76.zip
Parent3bca23eaaa49fa01f60ecbc5e0a329d29f088506 (diff)
feat!: Initial web server base implementation
12 files changed, 459 insertions, 0 deletions
-rw-r--r--src/http/callback_http.c136
-rw-r--r--src/http/callback_http.h38
-rw-r--r--src/http/http_headers.h17
-rw-r--r--src/http/request_method.h30
-rw-r--r--src/http/response_code.h18
-rw-r--r--src/main.c34
-rw-r--r--src/sockets/callback_echo.c6
-rw-r--r--src/sockets/callback_echo.h8
-rw-r--r--src/sockets/socket_server.c99
-rw-r--r--src/sockets/socket_server.h31
-rw-r--r--src/utils/strings.c32
-rw-r--r--src/utils/strings.h10
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 */