diff --git a/src/arena.c b/src/arena.c index be471f0..7903538 100644 --- a/src/arena.c +++ b/src/arena.c @@ -1,58 +1,135 @@ #ifndef ARENA_ #define ARENA_ +#include #include #include #include -#define ARENA_BLOCK_SIZE 256 +#define ARENA_BLOCK_SIZE 128 +#define DEFAULT_ALIGNMENT (sizeof(void*)) #include "byte_slice.c" +struct arena_block { + uint8_t *ptr; + struct arena_block *prev; +}; + struct arena { uint8_t *ptr; size_t len; - size_t capacity; + size_t offset; + + struct arena_block *prev_blocks; }; int arena_init(struct arena *arena) { - arena->ptr = malloc(ARENA_BLOCK_SIZE); + arena->ptr = aligned_alloc(DEFAULT_ALIGNMENT, ARENA_BLOCK_SIZE); if (!arena->ptr) { return -1; } - arena->len = 0; - arena->capacity = ARENA_BLOCK_SIZE; + arena->len = ARENA_BLOCK_SIZE; + arena->offset = 0; + arena->prev_blocks = NULL; return 0; } void arena_deinit(struct arena *arena) { free(arena->ptr); arena->ptr = NULL; + + struct arena_block *current = arena->prev_blocks; + while (current) { + struct arena_block *prev = current->prev; + free(current->ptr); + free(current); + current = prev; + } + arena->prev_blocks = NULL; } -static uint64_t div_round_up(uint64_t a, uint64_t b) { - return a % b == 0 ? a / b * b : (a / b + 1) * b; +static bool is_power_of_two(uintptr_t x) { + return (x & (x-1)) == 0; } -void *arena_alloc(struct arena *arena, size_t size) { +static uintptr_t align_forward(uintptr_t ptr, uintptr_t align) { + assert(is_power_of_two(align)); + + uintptr_t modulo = ptr & (align - 1); + if (modulo != 0) { + return ptr + (align - modulo); + } else { + return ptr; + } +} + +static uint32_t arena_get_next_offset(struct arena *arena, size_t alignemnt) { + return align_forward((uintptr_t)arena->ptr + arena->offset, alignemnt) - (uintptr_t)arena->ptr; +} + +static int arena_push_prev_block(struct arena *arena, void *ptr) { + struct arena_block *block = malloc(sizeof(struct arena_block)); + if (!block) { + free(ptr); + return -1; + } + + block->ptr = ptr; + block->prev = arena->prev_blocks; + arena->prev_blocks = block; + + return 0; +} + +void *arena_alloc_aligned(struct arena *arena, size_t size, size_t alignemnt) { if (arena->ptr == NULL && arena_init(arena)) { return NULL; } - if (arena->len + size > arena->capacity) { - size_t new_capacity = div_round_up(arena->len + size, ARENA_BLOCK_SIZE); - void *new_ptr = realloc(arena->ptr, new_capacity); + // Just allocate the request amount if it is larger than a block. + if (size >= ARENA_BLOCK_SIZE) { + void *ptr = aligned_alloc(alignemnt, size); + if (!ptr) { + return NULL; + } + + if (arena_push_prev_block(arena, ptr)) { + free(ptr); + return NULL; + } + + return ptr; + } + + // Start a new block, if allocation will not fit + if (arena_get_next_offset(arena, alignemnt) + size > arena->len) { + void *new_ptr = aligned_alloc(DEFAULT_ALIGNMENT, ARENA_BLOCK_SIZE); if (!new_ptr) { return NULL; } + + if (arena_push_prev_block(arena, arena->ptr)) { + free(new_ptr); + return NULL; + } + arena->ptr = new_ptr; - arena->capacity = new_capacity; + arena->offset = 0; + arena->len = ARENA_BLOCK_SIZE; } - void *result = (void*)&arena->ptr[arena->len]; - arena->len += size; - return result; + uint32_t offset = arena_get_next_offset(arena, alignemnt); + assert(offset + size <= arena->len); + void *ptr = &arena->ptr[offset]; + arena->offset = offset + size; + + return ptr; +} + +void *arena_alloc(struct arena *arena, size_t size) { + return arena_alloc_aligned(arena, size, DEFAULT_ALIGNMENT); } void *arena_dupe(struct arena *arena, void *ptr, size_t len) { diff --git a/src/byte_slice.c b/src/byte_slice.c index 8af377d..7d653dd 100644 --- a/src/byte_slice.c +++ b/src/byte_slice.c @@ -3,10 +3,13 @@ #include #include -#include +#include #include +#include "utils.c" + #define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) struct byte_slice { uint8_t *ptr; @@ -113,4 +116,27 @@ void byte_slice_trim_str(struct byte_slice *slice, const char *str) { byte_slice_trim(slice, str_slice); } +void byte_slice_print(struct byte_slice slice) { + printf("%.*s", (int)slice.len, slice.ptr); +} + +int byte_slice_to_uint32(struct byte_slice slice, uint32_t *result) { + if (slice.len == 0) { + return -1; + } + + *result = 0; + for (size_t i = 0; i < slice.len; i++) { + uint8_t c = slice.ptr[i]; + if (!is_digit(c)) { + return -1; + } + + (*result) *= 10; + (*result) += c - '0'; + } + + return 0; +} + #endif //BYTE_SLICE_ diff --git a/src/byte_slice_writer.c b/src/byte_slice_writer.c new file mode 100644 index 0000000..8d5e8ca --- /dev/null +++ b/src/byte_slice_writer.c @@ -0,0 +1,49 @@ +#ifndef BUFFER_WRITER_ +#define BUFFER_WRITER_ + +#include "byte_slice.c" +#include +#include + +struct byte_slice_writer { + struct byte_slice *buffer; + uint32_t capacity; +}; + +int writer_append(struct byte_slice_writer writer, struct byte_slice slice) { + if (writer.buffer->len + slice.len <= writer.capacity) { + memcpy(writer.buffer->ptr + writer.buffer->len, slice.ptr, slice.len); + writer.buffer->len += slice.len; + return 0; + } + + return -1; +} + +int writer_appendf(struct byte_slice_writer writer, const char *format, ...) { + int rc = -1; + + va_list args; + va_start(args, format); + int size = vsnprintf(NULL, 0, format, args); + va_end(args); + + if (writer.buffer->len + size + 1 <= writer.capacity) { + va_list args; + va_start(args, format); + vsnprintf( + (char*)writer.buffer->ptr + writer.buffer->len, + writer.capacity - writer.buffer->len, + format, + args + ); + writer.buffer->len += size; + va_end(args); + + return 0; + } + + return -1; +} + +#endif //BUFFER_WRITER_ diff --git a/src/client.c b/src/client.c index ba877c6..e3d422f 100644 --- a/src/client.c +++ b/src/client.c @@ -2,35 +2,70 @@ #define CLIENT_ #include +#include #include #include +#include -#include "parser.c" +#include "request_parser.c" #include "request.c" +#include "response.c" #include "log.c" +#include "byte_slice_writer.c" #define CLIENT_READ_BUFFER_SIZE 4096 +#define CLIENT_WRITE_BUFFER_SIZE 4096 -enum parser_state { - PARSER_STATE_REQUEST_LINE, - PARSER_STATE_HEADERS, - PARSER_STATE_BODY, - PARSER_STATE_DONE, +enum http_client_state { + CLIENT_STATE_INIT, + CLIENT_STATE_REQUEST_LINE, + CLIENT_STATE_HEADERS, + CLIENT_STATE_BODY, + CLIENT_STATE_REQUEST_DONE, + CLIENT_STATE_WAIT_FOR_RESPONSE, + CLIENT_STATE_SEND_RESPONSE, + CLIENT_STATE_RESPONSE_DONE, }; struct http_client { int fd; + bool close; struct sockaddr_storage addr; socklen_t addr_len; + // TODO: Make this buffer dynamic uint8_t read_bufer[CLIENT_READ_BUFFER_SIZE]; size_t read_bufer_len; - enum parser_state parse_state; - struct http_request parse_request; + // TODO: Make this buffer dynamic + uint8_t write_bufer[CLIENT_WRITE_BUFFER_SIZE]; + size_t write_bufer_len; + + // TODO: Maybe response and request should share the same memory arena? + enum http_client_state state; + struct http_request request; + struct http_response response; }; +void http_client_deinit(struct http_client *client) { + if (client->fd > 0) { + close(client->fd); + client->fd = 0; + } + + http_request_deinit(&client->request); + http_response_deinit(&client->response); +} + +bool http_client_can_recv(struct http_client *client) { + return client->read_bufer_len < CLIENT_READ_BUFFER_SIZE; +} + +bool http_client_can_send(struct http_client *client) { + return client->write_bufer_len > 0; +} + int http_client_recv(struct http_client *client) { int result = recv( client->fd, @@ -41,81 +76,193 @@ int http_client_recv(struct http_client *client) { if (result == 0) { // TODO: Mark connection for closing + return -1; } else if (result < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // TODO: Ignore error + return 0; } else if (errno == ECONNRESET) { // TODO: Mark connection for closing + return -1; + } else { + // TODO: Research what else can happen + return -1; } - } else { - struct http_request *request = &client->parse_request; + } - client->read_bufer_len += result; - log_debug("Received %d bytes", result); + struct http_request *request = &client->request; - struct byte_slice buffer = byte_slice_init(client->read_bufer, client->read_bufer_len); - struct byte_slice line = { 0 }; - while (buffer.len > 0) { - if (client->parse_state == PARSER_STATE_REQUEST_LINE) { - if (find_next_line(buffer, &line)) { - break; - } + client->read_bufer_len += result; - log_debug("request-line: %.*s", line.len, (char*)line.ptr); + struct byte_slice buffer = byte_slice_init(client->read_bufer, client->read_bufer_len); + while (true) { + if (client->state == CLIENT_STATE_INIT) { + http_request_deinit(&client->request); + http_response_deinit(&client->response); - if (parse_request_line(line, &request->method, &request->uri, &request->version)) { - remove_line(&buffer, line); - break; - } + client->response.status_code = 200; - log_debug("- method: '%.*s'", request->method.len, (char*)request->method.ptr); - log_debug("- uri: '%.*s'", request->uri.len, (char*)request->uri.ptr); - log_debug("- version: 'HTTP/%d.%d'", request->version.major, request->version.minor); + client->state = CLIENT_STATE_REQUEST_LINE; - remove_line(&buffer, line); - client->parse_state = PARSER_STATE_HEADERS; - - } else if (client->parse_state == PARSER_STATE_HEADERS) { - if (find_next_line(buffer, &line)) { - break; - } - - if (line.len == 0) { - client->parse_state = PARSER_STATE_BODY; - remove_line(&buffer, line); - continue; - } - - struct http_header header = { 0 }; - if (parse_header_line(line, &header)) { - remove_line(&buffer, line); - break; - } - - log_debug("header: %.*s: %.*s", header.name.len, (char*)header.name.ptr, header.value.len, (char*)header.value.ptr); - - if (http_request_append_header(request, header)) { - remove_line(&buffer, line); - break; - } - - remove_line(&buffer, line); - - } else if (client->parse_state == PARSER_STATE_BODY) { - - - log_debug("data: %.*s", buffer.len, (char*)buffer.ptr); - buffer.len = 0; - client->parse_state = PARSER_STATE_DONE; + } else if (client->state == CLIENT_STATE_REQUEST_LINE) { + struct byte_slice line = { 0 }; + if (find_next_line(buffer, &line)) { break; } - } - client->read_bufer_len = buffer.len; - log_debug("Left over %d bytes", client->read_bufer_len); + struct byte_slice method = { 0 }; + struct byte_slice uri = { 0 }; + if (parse_request_line(line, &method, &uri, &request->version)) { + remove_line(&buffer, line); + break; + } + + client->response.version = request->version; + + request->method = arena_dupe_slice(&request->arena, method); + if (request->method.len == 0) { + remove_line(&buffer, line); + break; + } + + request->uri = arena_dupe_slice(&request->arena, uri); + if (request->uri.len == 0) { + remove_line(&buffer, line); + break; + } + + remove_line(&buffer, line); + client->state = CLIENT_STATE_HEADERS; + + } else if (client->state == CLIENT_STATE_HEADERS) { + struct byte_slice line = { 0 }; + if (find_next_line(buffer, &line)) { + break; + } + + if (line.len == 0) { + client->state = CLIENT_STATE_BODY; + remove_line(&buffer, line); + continue; + } + + struct http_header header = { 0 }; + if (parse_header_line(line, &header)) { + remove_line(&buffer, line); + break; + } + + if (http_request_append_header(request, header)) { + remove_line(&buffer, line); + break; + } + + remove_line(&buffer, line); + + } else if (client->state == CLIENT_STATE_BODY) { + + int content_length = http_headers_get_content_length(&request->headers); + struct byte_slice *transfer_encoding = http_request_find_header_str(request, "transfer-encoding"); + if (content_length != -1) { + if (content_length > 0) { + if (buffer.len < content_length) { + // Wait for more data + // TODO: This will not work if the amount of data sent is more than 'CLIENT_READ_BUFFER_SIZE' + break; + } + + struct byte_slice sub_buffer = byte_slice_init(buffer.ptr, content_length); + request->body = arena_dupe_slice(&request->arena, sub_buffer); + if (request->body.len == 0) { + return -1; + } + } + + buffer.len = 0; + client->state = CLIENT_STATE_REQUEST_DONE; + } else if (transfer_encoding) { + log_debug("Transfer encoding not supported"); + break; + } else { + client->state = CLIENT_STATE_REQUEST_DONE; + } + } else { + break; + } + } + + client->read_bufer_len = buffer.len; + + return 0; +} + +int http_client_send(struct http_client *client) { + int result = send( + client->fd, + client->write_bufer, + client->write_bufer_len, + MSG_DONTWAIT + ); + + if (result == 0) { + // TODO: Mark connection for closing + return -1; + } else if (result < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // TODO: Ignore error + return 0; + } else if (errno == ECONNRESET) { + // TODO: Mark connection for closing + return -1; + } else { + // TODO: Research what else can happen + return -1; + } + } + + memmove(client->write_bufer, client->write_bufer + result, client->write_bufer_len - result); + client->write_bufer_len -= result; + + if (client->state == CLIENT_STATE_SEND_RESPONSE && client->write_bufer_len == 0) { + client->state = CLIENT_STATE_RESPONSE_DONE; + client->close = true; } return 0; } +void http_client_respond(struct http_client *client) { + if (client->state != CLIENT_STATE_WAIT_FOR_RESPONSE) { + return; + } + + struct byte_slice write_buffer = byte_slice_init(client->write_bufer, client->write_bufer_len); + struct byte_slice_writer writer = { + .buffer = &write_buffer, + .capacity = CLIENT_WRITE_BUFFER_SIZE + }; + + struct http_response *response = &client->response; + + writer_appendf(writer, "HTTP/%d.%d %d\r\n", response->version.major, response->version.minor, response->status_code); + + for (size_t i = 0; i < response->headers.len; i++) { + struct http_header *header = &response->headers.ptr[i]; + writer_append(writer, header->name); // TODO: Assert that name contains only valid characters + writer_appendf(writer, ": "); + writer_append(writer, header->value); // TODO: Assert that value contains only valid characters + writer_appendf(writer, "\r\n"); + } + + if (response->body.len > 0) { + writer_appendf(writer, "Content-Length: %d\r\n", response->body.len); + writer_appendf(writer, "\r\n"); + + writer_append(writer, response->body); + } + + client->write_bufer_len = write_buffer.len; + client->state = CLIENT_STATE_SEND_RESPONSE; +} + #endif //CLIENT_ diff --git a/src/headers.c b/src/headers.c new file mode 100644 index 0000000..cf8ac2b --- /dev/null +++ b/src/headers.c @@ -0,0 +1,143 @@ +#ifndef HEADERS_ +#define HEADERS_ + +#include +#include +#include + +#include "arena.c" +#include "utils.c" + +struct http_header { + struct byte_slice name; + struct byte_slice value; +}; + +struct http_headers { + struct http_header *ptr; + size_t len; + size_t capacity; +}; + +void http_headers_deinit(struct http_headers *headers) { + memset(headers, 0, sizeof(*headers)); +} + +int http_headers_append(struct http_headers *headers, struct arena *arena, struct http_header header) { + if (headers->len + 1 > headers->capacity) { + size_t new_capacity = headers->capacity * 1.5; + if (new_capacity == 0) { + new_capacity = 8; + } + + struct http_header *new_ptr = arena_alloc(arena, new_capacity * sizeof(struct http_header)); + if (!new_ptr) { + return -1; + } + + if (headers->ptr) { + memcpy(new_ptr, headers->ptr, headers->len * sizeof(struct http_header)); + } + + headers->ptr = new_ptr; + headers->capacity = new_capacity; + } + + struct byte_slice name = arena_dupe_slice(arena, header.name); + if (name.len == 0) { + return -1; + } + + struct byte_slice value = arena_dupe_slice(arena, header.value); + if (value.len == 0) { + return -1; + } + + headers->ptr[headers->len] = (struct http_header){ + .name = name, + .value = value, + }; + headers->len++; + + return 0; +} + +static bool case_insensitive_eql(struct byte_slice a, struct byte_slice b) { + if (a.len != b.len) { + return false; + } + + for (size_t i = 0; i < a.len; i++) { + uint8_t a_char = a.ptr[i]; + uint8_t b_char = b.ptr[i]; + + if (is_alpha(a_char)) { + a_char = toupper(a_char); + } + + if (is_alpha(b_char)) { + b_char = toupper(b_char); + } + + if (a_char != b_char) { + return false; + } + } + + return true; +} + +struct byte_slice *http_headers_find(struct http_headers *headers, struct byte_slice name) { + for (size_t i = 0; i < headers->len; i++) { + if (case_insensitive_eql(headers->ptr[i].name, name)) { + return &headers->ptr[i].value; + } + } + + return NULL; +} + +int http_headers_put(struct http_headers *headers, struct arena *arena, struct http_header header) { + struct byte_slice *existing_value = http_headers_find(headers, header.name); + if (existing_value != NULL) { + struct byte_slice value = arena_dupe_slice(arena, header.value); + if (value.len == 0) { + return -1; + } + + *existing_value = value; + return 0; + } else { + return http_headers_append(headers, arena, header); + } +} + +int http_headers_get_content_length(struct http_headers *headers) { + struct byte_slice name = byte_slice_init_str("content-length"); + struct byte_slice *value = http_headers_find(headers, name); + if (!value) { + return -1; + } + + uint32_t content_length = 0; + if (byte_slice_to_uint32(*value, &content_length)) { + return -1; + } + + return content_length; +} + +void http_header_print(struct http_header header) { + byte_slice_print(header.name); + printf(": "); + byte_slice_print(header.value); +} + +void http_headers_print(struct http_headers headers) { + for (size_t i = 0; i< headers.len; i++) { + http_header_print(headers.ptr[i]); + printf("\n"); + } +} + +#endif //HEADERS_ diff --git a/src/http_version.c b/src/http_version.c new file mode 100644 index 0000000..9434acf --- /dev/null +++ b/src/http_version.c @@ -0,0 +1,11 @@ +#ifndef HTTP_VERSION_ +#define HTTP_VERSION_ + +#include + +struct http_version { + uint8_t major; + uint8_t minor; +}; + +#endif //HTTP_VERSION_ diff --git a/src/main.c b/src/main.c index 5f28cc6..18c8214 100644 --- a/src/main.c +++ b/src/main.c @@ -4,16 +4,28 @@ #include #include #include +#include #include #include "server.c" +struct http_server server = { 0 }; + +void sighandler(int sig) { + http_server_deinit(&server); + exit(0); +} + int main() { + signal(SIGPIPE, SIG_IGN); + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + + if (http_server_init(&server)) { + return -1; + } + uint16_t server_port = 8080; - - struct http_server server = { 0 }; - http_server_init(&server); - if (http_server_listen(&server, server_port)) { printf("Failed to listen on port %d\n", server_port); return -1; @@ -22,8 +34,8 @@ int main() { printf("Listening on port %d\n", server_port); while (true) { - struct http_request request = { 0 }; - enum poll_result result = http_server_poll_request(&server, &request, -1); + struct http_client *client = NULL; + enum poll_result result = http_server_poll_request(&server, &client, -1); if (result == POLL_RESULT_TIMEOUT) { log_debug("Poll timeout\n"); continue; @@ -32,7 +44,17 @@ int main() { break; } - printf("Got request\n"); + struct byte_slice uri = client->request.uri; + if (byte_slice_eql_str(uri, "/secret")) { + http_response_append_header(&client->response, (struct http_header){ + .name = byte_slice_init_str("Secret-header"), + .value = byte_slice_init_str("Juicy secret"), + }); + http_response_set_body(&client->response, byte_slice_init_str("Hello, World! SECRET!\n")); + } else { + http_response_set_body(&client->response, byte_slice_init_str("Hello, World!\n")); + } + http_client_respond(client); } http_server_deinit(&server); diff --git a/src/request.c b/src/request.c index 7b223c5..76aeca1 100644 --- a/src/request.c +++ b/src/request.c @@ -1,22 +1,13 @@ #ifndef REQUEST_ #define REQUEST_ -#include #include +#include #include "byte_slice.c" #include "arena.c" -#include "utils.c" - -struct http_version { - uint8_t major; - uint8_t minor; -}; - -struct http_header { - struct byte_slice name; - struct byte_slice value; -}; +#include "headers.c" +#include "http_version.c" struct http_request { struct arena arena; @@ -27,85 +18,43 @@ struct http_request { struct http_version version; // Headers: - struct http_header *headers; - size_t headers_len; - size_t headers_capacity; + struct http_headers headers; // Body: struct byte_slice body; }; -int http_request_append_header(struct http_request *request, struct http_header header) { - if (request->headers_len >= request->headers_capacity) { - size_t new_capacity = request->headers_capacity * 1.5; - if (new_capacity == 0) { - new_capacity = 16; - } +void http_request_deinit(struct http_request *request) { + arena_deinit(&request->arena); + http_headers_deinit(&request->headers); - struct http_header *new_headers = arena_alloc(&request->arena, new_capacity * sizeof(struct http_header)); - if (!new_headers) { - return -1; - } - - if (request->headers) { - memcpy(new_headers, request->headers, request->headers_capacity * sizeof(struct http_header)); - } - - request->headers = new_headers; - request->headers_capacity = new_capacity; - } - - struct byte_slice name = arena_dupe_slice(&request->arena, header.name); - if (name.len == 0) { - return -1; - } - - struct byte_slice value = arena_dupe_slice(&request->arena, header.value); - if (value.len == 0) { - return -1; - } - - request->headers[request->headers_len] = (struct http_header){ - .name = name, - .value = value, - }; - request->headers_len++; - - return 0; + memset(request, 0, sizeof(struct http_request)); } -static bool case_insensitive_eql(struct byte_slice a, struct byte_slice b) { - if (a.len != b.len) { - return false; - } - for (size_t i = 0; i < a.len; i++) { - uint8_t a_char = a.ptr[i]; - uint8_t b_char = b.ptr[i]; - - if (is_alpha(a_char)) { - a_char = toupper(a_char); - } - - if (is_alpha(b_char)) { - b_char = toupper(b_char); - } - - if (a_char != b_char) { - return false; - } - } - - return true; +int http_request_append_header(struct http_request *request, struct http_header header) { + return http_headers_append(&request->headers, &request->arena, header); } struct byte_slice *http_request_find_header(struct http_request *request, struct byte_slice name) { - for (size_t i = 0; i < request->headers_len; i++) { - if (case_insensitive_eql(request->headers[i].name, name)) { - return &request->headers[i].value; - } - } + return http_headers_find(&request->headers, name); +} - return NULL; +struct byte_slice *http_request_find_header_str(struct http_request *request, const char *name) { + struct byte_slice str_slice = byte_slice_init_str((char*)name); + return http_request_find_header(request, str_slice); +} + +void http_request_print(struct http_request *request) { + byte_slice_print(request->method); + printf(" "); + byte_slice_print(request->uri); + printf(" HTTP/%d.%d\n", request->version.major, request->version.minor); + http_headers_print(request->headers); + if (request->body.len > 0) { + printf("\n"); + byte_slice_print(request->body); + printf("\n"); + } } #endif //REQUEST_ diff --git a/src/parser.c b/src/request_parser.c similarity index 100% rename from src/parser.c rename to src/request_parser.c diff --git a/src/response.c b/src/response.c new file mode 100644 index 0000000..e5eb40f --- /dev/null +++ b/src/response.c @@ -0,0 +1,43 @@ +#ifndef RESPONSE_ +#define RESPONSE_ + +#include "arena.c" +#include "headers.c" +#include "http_version.c" + +struct http_response { + struct arena arena; + + uint16_t status_code; + struct http_version version; + struct http_headers headers; + struct byte_slice body; +}; + +void http_response_deinit(struct http_response *response) { + arena_deinit(&response->arena); + + http_headers_deinit(&response->headers); + memset(response, 0, sizeof(struct http_response)); +} + +int http_response_append_header(struct http_response *response, struct http_header header) { + return http_headers_append(&response->headers, &response->arena, header); +} + +int http_response_set_body(struct http_response *response, struct byte_slice body) { + if (body.len == 0) { + response->body = byte_slice_init_zero(); + return 0; + } + + struct byte_slice body_dupe = arena_dupe_slice(&response->arena, body); + if (body_dupe.len == 0) { + return -1; + } + + response->body = body_dupe; + return 0; +} + +#endif //RESPONSE_ diff --git a/src/server.c b/src/server.c index 45bfec9..0f48b17 100644 --- a/src/server.c +++ b/src/server.c @@ -38,11 +38,30 @@ int http_server_init(struct http_server *server) { return 0; } +void http_server_remove_client(struct http_server *server, size_t index) { + assert(index < server->clients_len); + + struct http_client *client = &server->clients[index]; + + // log_debug("Closing connection"); + http_client_deinit(client); + + // TODO: Maybe don't do swap remove? Returned request pointers can become invalidated + if (server->clients_len > 1) { + server->clients[index] = server->clients[server->clients_len-1]; + } + server->clients_len--; +} + void http_server_deinit(struct http_server *server) { if (!server) { return; } + while (server->clients_len > 0) { + http_server_remove_client(server, 0); + } + free(server->clients); server->clients = NULL; @@ -84,51 +103,58 @@ err: return -1; } -struct http_client *http_server_get_unused_client(struct http_server *server) { +struct http_client *http_server_append_client(struct http_server *server, int fd, struct sockaddr *addr, socklen_t addr_len) { if (server->clients_len >= server->clients_capacity) { return NULL; } struct http_client *client = &server->clients[server->clients_len++]; memset(client, 0, sizeof(*client)); + + client->fd = fd; + memcpy(&client->addr, addr, addr_len); + client->addr_len = addr_len; return client; } -enum poll_result http_server_poll_request(struct http_server *server, struct http_request *result_request, int timeout) { +enum poll_result http_server_poll_request(struct http_server *server, struct http_client **result_client, int timeout) { enum poll_result result = POLL_RESULT_ERROR; if (server->fd == 0) { goto end; } - struct pollfd *pollfds = NULL; + struct pollfd *pollfds = calloc(1 + server->clients_capacity, sizeof(struct pollfd)); + if (!pollfds) { + goto end; + } + + pollfds[0].fd = server->fd; + pollfds[0].events = POLLIN; + while (true) { for (size_t i = 0; i < server->clients_len; i++) { struct http_client *client = &server->clients[i]; - if (client->parse_state == PARSER_STATE_DONE) { + if (client->state == CLIENT_STATE_REQUEST_DONE) { result = POLL_RESULT_REQUEST; - *result_request = client->parse_request; + client->state = CLIENT_STATE_WAIT_FOR_RESPONSE; + (*result_client) = client; return 0; } } - pollfds = calloc(1 + server->clients_capacity, sizeof(struct pollfd)); - if (!pollfds) { - goto end; - } - size_t fds_count = 1 + server->clients_len; - memset(pollfds, 0, sizeof(struct pollfd) * fds_count); - - pollfds[0].fd = server->fd; - pollfds[0].events = POLLIN; for (size_t i = 0; i < server->clients_len; i++) { struct http_client *client = &server->clients[i]; struct pollfd *pollfd = &pollfds[i+1]; pollfd->fd = server->clients[i].fd; - if (client->read_bufer_len < CLIENT_READ_BUFFER_SIZE) { - pollfd->events = POLLIN; + pollfd->events = 0; + if (http_client_can_recv(client)) { + pollfd->events |= POLLIN; + } + if (http_client_can_send(client)) { + pollfd->events |= POLLOUT; } } @@ -145,19 +171,20 @@ enum poll_result http_server_poll_request(struct http_server *server, struct htt struct pollfd *server_pollfd = &pollfds[0]; if (server_pollfd->revents & POLLIN) { - struct sockaddr_storage client_addr = { 0 }; - socklen_t client_addr_len = sizeof(client_addr); + struct sockaddr_storage addr = { 0 }; + socklen_t addr_len = sizeof(addr); - int client_fd = accept(server->fd, (struct sockaddr *)&client_addr, &client_addr_len); + int client_fd = accept(server->fd, (struct sockaddr *)&addr, &addr_len); if (client_fd == -1) { perror("accept"); } else { - log_debug("Accepted connection"); - struct http_client *client = http_server_get_unused_client(server); - assert(client != NULL); - client->fd = client_fd; - client->addr = client_addr; - client->addr_len = client_addr_len; + struct http_client *client = http_server_append_client(server, client_fd, (struct sockaddr *)&addr, addr_len); + if (client) { + // log_debug("Accepted connection"); + } else { + log_debug("Failed to accept connection: Max clients reached"); + close(client_fd); + } } } @@ -166,18 +193,29 @@ enum poll_result http_server_poll_request(struct http_server *server, struct htt struct http_client *client = &server->clients[i - 1]; if (pollfd->revents & POLLIN) { - http_client_recv(client); + if (http_client_recv(client)) { + client->close = true; + } + } + if (pollfd->revents & POLLOUT) { + if (http_client_send(client)) { + client->close = true; + } } if (pollfd->revents & (POLLHUP | POLLERR | POLLERR)) { - log_debug("Closing connection"); + client->close = true; } } - free(pollfds); - pollfds = NULL; + for (size_t i = 0; i < server->clients_len; i++) { + struct http_client *client = &server->clients[i]; + if (client->close) { + http_server_remove_client(server, i); + i--; + } + } } - end: free(pollfds); return result;