first commit! 🎉

This commit is contained in:
ari melody 2025-09-21 13:16:13 +01:00
commit 1b584f3a33
Signed by: ari
GPG key ID: CF99829C92678188
9 changed files with 683 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
http.dSYM/
http

7
src/appstate.h Normal file
View file

@ -0,0 +1,7 @@
#include "log.h"
typedef struct AppState {
int sock;
Logger *log;
} AppState;
static AppState app = {0};

238
src/http.c Normal file
View file

@ -0,0 +1,238 @@
#include "http.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
char *http_method_string(HTTPMethod method) {
switch (method) {
case METHOD_GET:
return METHOD_GET_STR;
case METHOD_HEAD:
return METHOD_HEAD_STR;
case METHOD_POST:
return METHOD_POST_STR;
case METHOD_PUT:
return METHOD_PUT_STR;
case METHOD_DELETE:
return METHOD_DELETE_STR;
case METHOD_CONNECT:
return METHOD_CONNECT_STR;
case METHOD_OPTIONS:
return METHOD_OPTIONS_STR;
case METHOD_TRACE:
return METHOD_TRACE_STR;
case METHOD_PATCH:
return METHOD_PATCH_STR;
default:
return "???";
}
}
int parse_method(HTTPRequest *request, char *data, size_t data_len) {
char *end = strchr(data, ' ');
if (end == NULL)
return -1;
size_t len = (end - data) % 8;
char method[len + 1];
strncpy(method, data, len);
method[len] = 0;
if (strcmp(method, METHOD_GET_STR) == 0)
request->method = METHOD_GET;
else if (strcmp(method, METHOD_HEAD_STR) == 0)
request->method = METHOD_HEAD;
else if (strcmp(method, METHOD_POST_STR) == 0)
request->method = METHOD_POST;
else if (strcmp(method, METHOD_PUT_STR) == 0)
request->method = METHOD_PUT;
else if (strcmp(method, METHOD_DELETE_STR) == 0)
request->method = METHOD_DELETE;
else if (strcmp(method, METHOD_CONNECT_STR) == 0)
request->method = METHOD_CONNECT;
else if (strcmp(method, METHOD_OPTIONS_STR) == 0)
request->method = METHOD_OPTIONS;
else if (strcmp(method, METHOD_TRACE_STR) == 0)
request->method = METHOD_TRACE;
else if (strcmp(method, METHOD_PATCH_STR) == 0)
request->method = METHOD_PATCH;
else
return -1;
return 0;
}
int parse_request_uri(HTTPRequest *request, char *data, size_t data_len) {
char *start = strstr(data, " /");
if (start == NULL)
return -1;
start++;
char *end = strchr(start, ' ');
size_t len = end - start;
request->uri = malloc(sizeof(char) * (len + 1));
strncpy(request->uri, start, len);
request->uri[len] = 0;
return 0;
}
int parse_headers(HTTPRequest *request, char *data, size_t data_len) {
request->headers = NULL;
char *start = data;
while (start < data + data_len) {
char *end = strstr(start, "\r\n");
if (end == NULL)
break;
size_t linelen = end - start;
char line[linelen + 1];
strncpy(line, start, linelen);
line[linelen] = 0;
char *keyend = strstr(line, ":");
if (keyend == NULL)
break;
char *valstart = keyend + 1;
while (*valstart == ' ' && valstart < line + linelen)
valstart++;
if (valstart == NULL || valstart >= line + linelen)
break;
size_t keylen = keyend - line;
size_t valuelen = linelen - (valstart - line);
HTTPHeader *header = malloc(sizeof(HTTPHeader));
header->key = malloc(sizeof(char) * (keylen + 1));
header->value = malloc(sizeof(char) * (valuelen + 1));
strncpy(header->key, line, keylen);
header->key[keylen] = 0;
strncpy(header->value, valstart, valuelen);
header->value[valuelen] = 0;
request->headers = add_node(request->headers, header->key, header);
start += linelen + 2; // skip \r\n
}
return 0;
}
void _free_header_nodes(Node *tree) {
if (tree == NULL)
return;
_free_header_nodes(tree->left);
_free_header_nodes(tree->right);
HTTPHeader *header = tree->data;
free(header->key);
free(header->value);
free(tree);
}
void free_http_request(HTTPRequest *request) {
free(request->body);
_free_header_nodes(request->headers);
free(request);
}
void _push_http_header_list(Node *tree, HTTPHeader **list, size_t index) {
if (tree == NULL)
return;
_push_http_header_list(tree->right, list, index);
list[index + 1] = tree->data;
_push_http_header_list(tree->left, list, index + 2);
}
size_t list_http_headers(HTTPRequest *request, HTTPHeader **list) {
size_t len = tree_size(request->headers);
list = malloc(sizeof(HTTPHeader*) * len);
_push_http_header_list(request->headers, list, 0);
return len;
}
char *get_http_header(HTTPRequest *request, char *key) {
Node *node = get_node(request->headers, key);
if (node == NULL)
return NULL;
HTTPHeader *header = node->data;
if (header == NULL)
return NULL;
return header->value;
}
int recvhttp(int sock, HTTPRequest *request, struct sockaddr *addr, socklen_t *addr_len) {
size_t buflen = RECV_BUFFER_SIZE;
char *recvbuf = malloc(buflen);
memset(recvbuf, 0, buflen);
size_t total_bytes = 0;
int bytes = 0;
while (1) {
bytes = recvfrom(
sock,
recvbuf + total_bytes,
RECV_BUFFER_SIZE,
0,
addr,
addr_len);
if (bytes == -1) {
fprintf(stderr, "recv failed: %s\n", strerror(errno));
return -1;
}
total_bytes += bytes;
// HTTP request end
if (total_bytes >= 4 && strcmp(recvbuf + total_bytes - 4, "\r\n\r\n") == 0)
break;
if (bytes < RECV_BUFFER_SIZE)
break;
// add to buffer size if we expect more data
size_t newbuflen = buflen + RECV_BUFFER_SIZE;
char *newbuf = malloc(newbuflen);
memset(newbuf, 0, newbuflen);
memcpy(newbuf, recvbuf, buflen);
free(recvbuf);
recvbuf = newbuf;
buflen = newbuflen;
}
if (recvbuf[total_bytes] != 0) {
if (total_bytes == buflen) {
size_t newbuflen = buflen + 1;
char *newbuf = malloc(newbuflen);
memcpy(newbuf, recvbuf, buflen);
free(recvbuf);
recvbuf = newbuf;
buflen = newbuflen;
}
recvbuf[total_bytes] = 0;
}
if (parse_method(request, recvbuf, total_bytes) == -1) {
fprintf(stderr, "failed to parse request method\n");
return -1;
}
if (parse_request_uri(request, recvbuf, total_bytes) == -1) {
fprintf(stderr, "failed to parse request uri\n");
return -1;
}
char *header_start = strstr(recvbuf, "\r\n");
if (header_start == NULL || (header_start - recvbuf) + 2 > total_bytes) {
fprintf(stderr, "warn: client with no headers?\n");
} else {
header_start += 2;
if (parse_headers(request, header_start, total_bytes) == -1) {
fprintf(stderr, "failed to parse request headers\n");
return -1;
}
}
request->body = strstr(recvbuf, "\r\n\r\n");
request->body_len = request->body != NULL ? strlen(request->body) : 0;
return 0;
}

117
src/http.h Normal file
View file

@ -0,0 +1,117 @@
#include "node.h"
#include <arpa/inet.h>
#define SOCK_QUEUE 1024
#define RECV_BUFFER_SIZE 1024 // starting length; buffer size will be increased as needed
#define METHOD_GET_STR "GET"
#define METHOD_HEAD_STR "HEAD"
#define METHOD_POST_STR "POST"
#define METHOD_PUT_STR "PUT"
#define METHOD_DELETE_STR "DELETE"
#define METHOD_CONNECT_STR "CONNECT"
#define METHOD_OPTIONS_STR "OPTIONS"
#define METHOD_TRACE_STR "TRACE"
#define METHOD_PATCH_STR "PATCH"
typedef enum HTTPMethod {
METHOD_GET,
METHOD_HEAD,
METHOD_POST,
METHOD_PUT,
METHOD_DELETE,
METHOD_CONNECT,
METHOD_OPTIONS,
METHOD_TRACE,
METHOD_PATCH,
} HTTPMethod;
#define HTTP_Continue 100
#define HTTP_Switching_Protocols 101
#define HTTP_Processing 102
#define HTTP_Early_Hints 103
#define HTTP_OK 200
#define HTTP_Created 201
#define HTTP_Accepted 202
#define HTTP_Non_Authoritative_Information 203
#define HTTP_No_Content 204
#define HTTP_Reset_Content 205
#define HTTP_Partial_Content 206
#define HTTP_Multi_Status 207
#define HTTP_Already_Reported 208
#define HTTP_IM_Used 226
#define HTTP_Multiple_Choices 300
#define HTTP_Moved_Permanently 301
#define HTTP_Found 302
#define HTTP_See_Other 303
#define HTTP_Not_Modified 304
#define HTTP_Temporary_Redirect 307
#define HTTP_Permanent_Redirect 308
#define HTTP_Bad_Request 400
#define HTTP_Unauthorized 401
#define HTTP_Payment_Required 402
#define HTTP_Forbidden 403
#define HTTP_Not_Found 404
#define HTTP_Method_Not_Allowed 405
#define HTTP_Not_Acceptable 406
#define HTTP_Proxy_Authentication_Required 407
#define HTTP_Request_Timeout 408
#define HTTP_Conflict 409
#define HTTP_Gone 410
#define HTTP_Length_Required 411
#define HTTP_Precondition_Failed 412
#define HTTP_Content_Too_Large 413
#define HTTP_URI_Too_Long 414
#define HTTP_Unsupported_Media_Type 415
#define HTTP_Range_Not_Satisfiable 416
#define HTTP_Expectation_Failed 417
#define HTTP_TEAPOT 418
#define HTTP_Misdirected_Request 421
#define HTTP_Unprocessable_Content 422
#define HTTP_Locked 423
#define HTTP_Failed_Dependency 424
#define HTTP_Too_Early 425
#define HTTP_Upgrade_Required 426
#define HTTP_Precondition_Required 428
#define HTTP_Too_Many_Requests 429
#define HTTP_Request_Header_Fields_Too_Large 431
#define HTTP_Unavailable_For_Legal_Reasons 451
#define HTTP_Internal_Server_Error 500
#define HTTP_Not_Implemented 501
#define HTTP_Bad_Gateway 502
#define HTTP_Service_Unavailable 503
#define HTTP_Gateway_Timeout 504
#define HTTP_HTTP_Version_Not_Supported 505
#define HTTP_Variant_Also_Negotiates 506
#define HTTP_Insufficient_Storage 507
#define HTTP_Loop_Detected 508
#define HTTP_Not_Extended 510
#define HTTP_Network_Authentication_Required 511
typedef struct HTTPRequest {
HTTPMethod method;
char *uri;
Node *headers;
struct sockaddr_in addr;
char *body;
size_t body_len;
} HTTPRequest;
typedef struct HTTPHeader {
char *key;
char *value;
} HTTPHeader;
static const char RESPONSE_OK[] =
"HTTP/1.1 200 OK\r\n"
"Server: ari's awesome server\r\n"
"Content-Length: %ld\r\n"
"\r\n"
"%s";
int recvhttp(int sock, HTTPRequest *request, struct sockaddr *addr, socklen_t *addr_len);
void free_http_request(HTTPRequest *request);
char *http_method_string(HTTPMethod method);
size_t list_http_headers(HTTPRequest *request, HTTPHeader **list);
char *get_http_header(HTTPRequest *request, char *key);

63
src/log.c Normal file
View file

@ -0,0 +1,63 @@
#include "log.h"
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#define TIMESTAMP_SIZE 32
void newlog(Logger *logger, const char *name, FILE *file) {
logger->name = name;
logger->file = file;
logger->lock = 0;
}
void _gettimestamp(char *result, size_t result_len) {
time_t timer = time(NULL);
struct tm *tm_info = localtime(&timer);
strftime(result, result_len, "%Y-%m-%d %H:%M:%S", tm_info);
}
void _logf(Logger *logger, const char *type, const char *format, va_list args) {
while (logger->lock != 0);
logger->lock = 1;
char timestamp[TIMESTAMP_SIZE] = {0};
_gettimestamp(timestamp, TIMESTAMP_SIZE);
fprintf(logger->file, "[%s] [%s] ", timestamp, type);
vfprintf(logger->file, format, args);
fprintf(logger->file, "\n");
logger->lock = 0;
}
void loginfo(Logger *logger, const char *format, ...) {
va_list args;
va_start(args, format);
_logf(logger, LOG_INFO, format, args);
va_end(args);
}
void logwarn(Logger *logger, const char *format, ...) {
va_list args;
va_start(args, format);
_logf(logger, LOG_WARN, format, args);
va_end(args);
}
void logerror(Logger *logger, const char *format, ...) {
va_list args;
va_start(args, format);
_logf(logger, LOG_ERROR, format, args);
va_end(args);
}
void logdebug(Logger *logger, const char *format, ...) {
#ifdef DEBUG
va_list args;
va_start(args, format);
_logf(logger, LOG_DEBUG, format, args);
va_end(args);
#else
#endif /* ifdef DEBUG */
}

19
src/log.h Normal file
View file

@ -0,0 +1,19 @@
#include <stdio.h>
#define LOG_INFO "INFO"
#define LOG_WARN "WARN"
#define LOG_ERROR "ERROR"
#define LOG_DEBUG "DEBUG"
typedef struct Logger {
const char *name;
FILE *file;
char lock;
} Logger;
void newlog(Logger *logger, const char *name, FILE *file);
void loginfo(Logger *logger, const char *format, ...);
void logwarn(Logger *logger, const char *format, ...);
void logerror(Logger *logger, const char *format, ...);
void logdebug(Logger *logger, const char *format, ...);

158
src/main.c Normal file
View file

@ -0,0 +1,158 @@
#include "appstate.h"
#include "http.h"
#include <_stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/_types/_socklen_t.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#define TIMESTAMP_SIZE 32
static void catch_signal(int signo) {
if (close(app.sock)) {
fprintf(stderr, "failed to close socket: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
int serve(AppState *app) {
struct sockaddr addr;
socklen_t addr_len = sizeof(struct sockaddr);
int accept_sock = accept(
app->sock,
&addr,
&addr_len);
if (accept_sock == -1) {
fprintf(stderr, "failed to accept socket connection: %s\n", strerror(errno));
return -1;
}
// TODO: hand off request to thread pool
HTTPRequest request;
if (recvhttp(accept_sock, &request, &addr, &addr_len) == -1) {
fprintf(stderr, "failed to receive HTTP request\n");
return -1;
}
char *straddr = inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr);
loginfo(app->log, "%s: %s %s [%s] (%ld bytes)",
straddr,
http_method_string(request.method),
request.uri,
get_http_header(&request, "User-Agent"),
request.body_len);
// printf("headers:\n");
// HTTPHeader **headers;
// size_t headers_len = list_http_headers(&request, headers);
// for (size_t i = 0; i < headers_len; i++) {
// HTTPHeader *header = headers[i];
// printf("%s: %s\n", header->key, header->value);
// }
char *response_body;
if (asprintf(&response_body, "Received %ld bytes from you!\n", request.body_len) == -1) {
fprintf(stderr, "failed to format response body: %s\n", strerror(errno));
return -1;
}
char *response;
if (asprintf(&response, RESPONSE_OK, strlen(response_body), response_body) == -1) {
fprintf(stderr, "failed to format response: %s\n", strerror(errno));
return -1;
}
free(response_body);
if (send(accept_sock, response, strlen(response), 0) == -1) {
fprintf(stderr, "failed to send data to socket: %s\n", strerror(errno));
return -1;
}
free(response);
if (close(accept_sock) == -1) {
fprintf(stderr, "failed to close client socket: %s\n", strerror(errno));
return -1;
}
return 0;
}
int create_server(struct in_addr in_addr, uint16_t port) {
struct sockaddr_in service;
memset(&service, 0, sizeof(struct sockaddr_in));
service.sin_family = AF_INET;
service.sin_addr = in_addr;
service.sin_port = htons(port);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
fprintf(stderr, "failed to create socket: %s\n", strerror(errno));
return -1;
}
if (bind(sock, (struct sockaddr *)&service, sizeof(struct sockaddr_in)) == -1) {
close(sock);
fprintf(stderr, "failed to bind to port: %s\n", strerror(errno));
return -1;
}
if (listen(sock, SOCK_QUEUE) == -1) {
close(sock);
fprintf(stderr, "failed to listen: %s\n", strerror(errno));
return -1;
}
return sock;
}
int main(int argc, char *argv[]) {
if (argc >= 2 && strcmp(argv[1], "help") == 0) {
printf("usage: %s <port> [address]\n", argv[0]);
exit(0);
}
if (argc < 2) {
fprintf(stderr, "usage: %s <port> [address]\n", argv[0]);
exit(EXIT_FAILURE);
}
if (signal(SIGINT, catch_signal) == SIG_ERR) {
fprintf(stderr, "failed to set signal handler: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
int err = 0;
in_port_t port = atoi(argv[1]);
char *straddr;
if (argc >= 3)
straddr = argv[2];
else
straddr = "0.0.0.0";
struct in_addr in_address;
inet_aton(straddr, &in_address);
int sock = create_server(in_address, port);
if (sock == -1)
exit(EXIT_FAILURE);
app.sock = sock;
app.log = malloc(sizeof(Logger));
newlog(app.log, "http", stdout);
printf("now listening on %s:%d\n", straddr, port);
while (serve(&app) != -1);
if (close(sock) == -1) {
fprintf(stderr, "failed to close socket: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

65
src/node.c Normal file
View file

@ -0,0 +1,65 @@
#include "node.h"
#include <stdlib.h>
#include <string.h>
Node *add_node(Node *tree, char *key, void *data) {
if (tree == NULL) {
tree = malloc(sizeof(Node));
tree->key = malloc(sizeof(char) * strlen(key));
strcpy(tree->key, key);
tree->data = data;
tree->left = tree->right = NULL;
return tree;
}
if (strcmp(tree->key, key) > 0)
tree->right = add_node(tree->right, key, data);
else
tree->left = add_node(tree->left, key, data);
return tree;
}
Node *get_node(Node *tree, char *key) {
if (tree == NULL)
return NULL;
int cmp = strcmp(tree->key, key);
if (cmp == 0)
return tree;
else if (cmp > 0)
return get_node(tree->right, key);
else
return get_node(tree->left, key);
}
size_t tree_size(Node *tree) {
if (tree == NULL)
return 0;
return tree_size(tree->left) + tree_size(tree->right) + 1;
}
void _push_node_list(Node *tree, Node **list, size_t index) {
if (tree == NULL)
return;
_push_node_list(tree->right, list, index);
list[index + 1] = tree;
_push_node_list(tree->left, list, index + 2);
}
size_t list_nodes(Node *tree, Node **list) {
if (tree == NULL)
return 0;
size_t len = tree_size(tree);
*list = malloc(sizeof(Node*) * len);
_push_node_list(tree, list, 0);
return len;
}
void free_node(Node *node) {
free_node(node->left);
free(node->key);
free(node->data);
free_node(node->right);
free(node);
}

14
src/node.h Normal file
View file

@ -0,0 +1,14 @@
#include <stddef.h>
typedef struct Node {
char *key;
void *data;
struct Node *left;
struct Node *right;
} Node;
Node *add_node(Node *tree, char *key, void *data);
Node *get_node(Node *tree, char *key);
void free_node(Node *node);
size_t list_nodes(Node *tree, Node **list);
size_t tree_size(Node *tree);