Compare commits
2 Commits
035fdb4568
...
a387896ff2
Author | SHA1 | Date | |
---|---|---|---|
a387896ff2 | |||
25730681a9 |
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# HTTP server in C
|
||||||
|
|
||||||
|
Run program:
|
||||||
|
```
|
||||||
|
zig build run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
* [Hypertext Transfer Protocol -- HTTP/1.1](https://www.rfc-editor.org/rfc/rfc2616)
|
||||||
|
* [HTTP Working Group](https://httpwg.org/)
|
157
src/arena.c
Normal file
157
src/arena.c
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#ifndef ARENA_
|
||||||
|
#define ARENA_
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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 offset;
|
||||||
|
|
||||||
|
struct arena_block *prev_blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
int arena_init(struct arena *arena) {
|
||||||
|
arena->ptr = aligned_alloc(DEFAULT_ALIGNMENT, ARENA_BLOCK_SIZE);
|
||||||
|
if (!arena->ptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool is_power_of_two(uintptr_t x) {
|
||||||
|
return (x & (x-1)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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->offset = 0;
|
||||||
|
arena->len = ARENA_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
void *new_ptr = arena_alloc(arena, len);
|
||||||
|
if (!new_ptr) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr) {
|
||||||
|
memcpy(new_ptr, ptr, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct byte_slice arena_dupe_slice(struct arena *arena, struct byte_slice slice) {
|
||||||
|
void *new_ptr = arena_dupe(arena, slice.ptr, slice.len);
|
||||||
|
if (!new_ptr) {
|
||||||
|
return byte_slice_init_zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
return byte_slice_init(new_ptr, slice.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //ARENA_
|
142
src/byte_slice.c
Normal file
142
src/byte_slice.c
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#ifndef BYTE_SLICE_
|
||||||
|
#define BYTE_SLICE_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct byte_slice byte_slice_init(uint8_t *ptr, size_t len) {
|
||||||
|
return (struct byte_slice){ .ptr = ptr, .len = len };
|
||||||
|
}
|
||||||
|
|
||||||
|
struct byte_slice byte_slice_init_zero() {
|
||||||
|
return (struct byte_slice){ 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
struct byte_slice byte_slice_init_str(char *str) {
|
||||||
|
return byte_slice_init((uint8_t*)str, str ? strlen(str) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_slice_move_left(struct byte_slice *slice, size_t amount) {
|
||||||
|
amount = MIN(amount, slice->len);
|
||||||
|
memmove(slice->ptr, slice->ptr + amount, slice->len - amount);
|
||||||
|
|
||||||
|
slice->len -= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_slice_find(struct byte_slice slice, uint8_t needle) {
|
||||||
|
for (size_t i = 0; i < slice.len; i++) {
|
||||||
|
if (slice.ptr[i] == needle) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_slice_split_once(struct byte_slice slice, uint8_t separator, struct byte_slice *left, struct byte_slice *right) {
|
||||||
|
int index = byte_slice_find(slice, separator);
|
||||||
|
if (index < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*left = byte_slice_init(slice.ptr, index);
|
||||||
|
*right = byte_slice_init(slice.ptr + index + 1, slice.len - index - 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool byte_slice_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++) {
|
||||||
|
if (a.ptr[i] != b.ptr[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool byte_slice_eql_str(struct byte_slice slice, const char *str) {
|
||||||
|
// yes, the 'const' is cast away, but that is fine.
|
||||||
|
// Because I know that the string won't be modified.
|
||||||
|
struct byte_slice str_slice = byte_slice_init_str((char *)str);
|
||||||
|
return byte_slice_eql(slice, str_slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool byte_slice_is_number(struct byte_slice slice) {
|
||||||
|
for (size_t i = 0; i < slice.len; i++) {
|
||||||
|
if (!('0' <= slice.ptr[i] && slice.ptr[i] <= '9')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_slice_trim_left(struct byte_slice *slice, struct byte_slice to_trim) {
|
||||||
|
size_t left = 0;
|
||||||
|
while (byte_slice_find(to_trim, slice->ptr[left]) != -1 && left < slice->len) {
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
|
||||||
|
slice->ptr += left;
|
||||||
|
slice->len -= left;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_slice_trim_right(struct byte_slice *slice, struct byte_slice to_trim) {
|
||||||
|
size_t right = 0;
|
||||||
|
while (byte_slice_find(to_trim, slice->ptr[slice->len - right - 1]) != -1 && right < slice->len) {
|
||||||
|
right++;
|
||||||
|
}
|
||||||
|
|
||||||
|
slice->len -= right;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_slice_trim(struct byte_slice *slice, struct byte_slice to_trim) {
|
||||||
|
byte_slice_trim_right(slice, to_trim);
|
||||||
|
byte_slice_trim_left(slice, to_trim);
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_slice_trim_str(struct byte_slice *slice, const char *str) {
|
||||||
|
// yes, the 'const' is cast away, but that is fine.
|
||||||
|
// Because I know that the string won't be modified.
|
||||||
|
struct byte_slice str_slice = byte_slice_init_str((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_
|
49
src/byte_slice_writer.c
Normal file
49
src/byte_slice_writer.c
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef BUFFER_WRITER_
|
||||||
|
#define BUFFER_WRITER_
|
||||||
|
|
||||||
|
#include "byte_slice.c"
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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_
|
268
src/client.c
Normal file
268
src/client.c
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
#ifndef CLIENT_
|
||||||
|
#define CLIENT_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#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 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;
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
client->read_bufer + client->read_bufer_len,
|
||||||
|
CLIENT_READ_BUFFER_SIZE - client->read_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct http_request *request = &client->request;
|
||||||
|
|
||||||
|
client->read_bufer_len += result;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
client->response.status_code = 200;
|
||||||
|
|
||||||
|
client->state = CLIENT_STATE_REQUEST_LINE;
|
||||||
|
|
||||||
|
} else if (client->state == CLIENT_STATE_REQUEST_LINE) {
|
||||||
|
struct byte_slice line = { 0 };
|
||||||
|
if (find_next_line(buffer, &line)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_
|
143
src/headers.c
Normal file
143
src/headers.c
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#ifndef HEADERS_
|
||||||
|
#define HEADERS_
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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_
|
11
src/http_version.c
Normal file
11
src/http_version.c
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef HTTP_VERSION_
|
||||||
|
#define HTTP_VERSION_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct http_version {
|
||||||
|
uint8_t major;
|
||||||
|
uint8_t minor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //HTTP_VERSION_
|
8
src/log.c
Normal file
8
src/log.c
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef LOG_
|
||||||
|
#define LOG_
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define log_debug(fmt, ...) printf("DEBUG: " fmt "\n", ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#endif //LOG_
|
58
src/main.c
58
src/main.c
@ -1,6 +1,62 @@
|
|||||||
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include "server.c"
|
||||||
|
|
||||||
|
struct http_server server = { 0 };
|
||||||
|
|
||||||
|
void sighandler(int sig) {
|
||||||
|
http_server_deinit(&server);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
printf("Hello, World!\n");
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
signal(SIGINT, sighandler);
|
||||||
|
signal(SIGTERM, sighandler);
|
||||||
|
|
||||||
|
if (http_server_init(&server)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t server_port = 8080;
|
||||||
|
if (http_server_listen(&server, server_port)) {
|
||||||
|
printf("Failed to listen on port %d\n", server_port);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Listening on port %d\n", server_port);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
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;
|
||||||
|
} else if (result == POLL_RESULT_TIMEOUT) {
|
||||||
|
log_debug("Poll failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
60
src/request.c
Normal file
60
src/request.c
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#ifndef REQUEST_
|
||||||
|
#define REQUEST_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "byte_slice.c"
|
||||||
|
#include "arena.c"
|
||||||
|
#include "headers.c"
|
||||||
|
#include "http_version.c"
|
||||||
|
|
||||||
|
struct http_request {
|
||||||
|
struct arena arena;
|
||||||
|
|
||||||
|
// Request line:
|
||||||
|
struct byte_slice method;
|
||||||
|
struct byte_slice uri;
|
||||||
|
struct http_version version;
|
||||||
|
|
||||||
|
// Headers:
|
||||||
|
struct http_headers headers;
|
||||||
|
|
||||||
|
// Body:
|
||||||
|
struct byte_slice body;
|
||||||
|
};
|
||||||
|
|
||||||
|
void http_request_deinit(struct http_request *request) {
|
||||||
|
arena_deinit(&request->arena);
|
||||||
|
http_headers_deinit(&request->headers);
|
||||||
|
|
||||||
|
memset(request, 0, sizeof(struct http_request));
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return http_headers_find(&request->headers, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_
|
149
src/request_parser.c
Normal file
149
src/request_parser.c
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#ifndef PARSER_
|
||||||
|
#define PARSER_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "byte_slice.c"
|
||||||
|
#include "request.c"
|
||||||
|
#include "utils.c"
|
||||||
|
|
||||||
|
int find_next_line(struct byte_slice buffer, struct byte_slice *line) {
|
||||||
|
int newline_pos = byte_slice_find(buffer, '\n');
|
||||||
|
if (newline_pos == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*line = byte_slice_init(buffer.ptr, newline_pos);
|
||||||
|
if (line->len > 0 && line->ptr[line->len-1] == '\r') {
|
||||||
|
line->len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_line(struct byte_slice *buffer, struct byte_slice line) {
|
||||||
|
if (buffer->ptr[line.len] == '\r') {
|
||||||
|
byte_slice_move_left(buffer, line.len + 2);
|
||||||
|
} else {
|
||||||
|
byte_slice_move_left(buffer, line.len + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_visible_char(uint8_t c) {
|
||||||
|
// Constants '0x21' and '0x7E' from https://www.rfc-editor.org/rfc/rfc5234.html#appendix-B.1
|
||||||
|
return 0x21 <= c && c <= 0x7E;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_obs_text(uint8_t c) {
|
||||||
|
// Constants '0x80' and '0xFF' from https://httpwg.org/specs/rfc9110.html#fields.values
|
||||||
|
return 0x80 <= c && c <= 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_token_char(uint8_t c) {
|
||||||
|
const char allowed_symbols[] = "!#$%&'*+-.^_`|~";
|
||||||
|
return is_digit(c) || is_alpha(c) || memchr(allowed_symbols, c, sizeof(allowed_symbols)-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_token(struct byte_slice slice) {
|
||||||
|
if (slice.len == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < slice.len; i++) {
|
||||||
|
if (!is_token_char(slice.ptr[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_header_value(struct byte_slice slice) {
|
||||||
|
for (size_t i = 0; i < slice.len; i++) {
|
||||||
|
uint8_t c = slice.ptr[i];
|
||||||
|
if (i > 0 && i < slice.len-1) {
|
||||||
|
if (c == '\t' || c == ' ') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool field_vchar = is_visible_char(c) || is_obs_text(c);
|
||||||
|
if (!field_vchar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_http_version(struct http_version *version, struct byte_slice version_slice) {
|
||||||
|
struct byte_slice http_constant = { 0 };
|
||||||
|
struct byte_slice major_minor = { 0 };
|
||||||
|
if (byte_slice_split_once(version_slice, '/', &http_constant, &major_minor)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!byte_slice_eql_str(http_constant, "HTTP")) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct byte_slice major = { 0 };
|
||||||
|
struct byte_slice minor = { 0 };
|
||||||
|
if (byte_slice_split_once(major_minor, '.', &major, &minor)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (major.len != 1 || !is_digit(major.ptr[0])) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (minor.len != 1 || !is_digit(minor.ptr[0])) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
version->major = major.ptr[0] - '0';
|
||||||
|
version->minor = minor.ptr[0] - '0';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_request_line(struct byte_slice line, struct byte_slice *method, struct byte_slice *uri, struct http_version *version) {
|
||||||
|
// TODO: Ignore whitespace at start and end of line
|
||||||
|
|
||||||
|
// TODO: Split by whitespace, allow multiple whitespace characters in a row
|
||||||
|
struct byte_slice uri_version = { 0 };
|
||||||
|
if (byte_slice_split_once(line, ' ', method, &uri_version)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Split by whitespace, allow multiple whitespace characters in a row
|
||||||
|
struct byte_slice version_slice = { 0 };
|
||||||
|
if (byte_slice_split_once(uri_version, ' ', uri, &version_slice)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parse_http_version(version, version_slice)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_header_line(struct byte_slice line, struct http_header *header) {
|
||||||
|
if (byte_slice_split_once(line, ':', &header->name, &header->value)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_token(header->name)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte_slice_trim_str(&header->value, " \t");
|
||||||
|
|
||||||
|
if (!is_header_value(header->value)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //PARSER_
|
43
src/response.c
Normal file
43
src/response.c
Normal file
@ -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_
|
222
src/server.c
Normal file
222
src/server.c
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "client.c"
|
||||||
|
#include "log.c"
|
||||||
|
|
||||||
|
enum poll_result {
|
||||||
|
POLL_RESULT_REQUEST,
|
||||||
|
POLL_RESULT_TIMEOUT,
|
||||||
|
POLL_RESULT_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct http_server {
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
struct http_version version;
|
||||||
|
|
||||||
|
struct http_client *clients;
|
||||||
|
size_t clients_len;
|
||||||
|
size_t clients_capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
int http_server_init(struct http_server *server) {
|
||||||
|
memset(server, 0, sizeof(*server));
|
||||||
|
|
||||||
|
server->clients_capacity = 10;
|
||||||
|
server->clients = calloc(server->clients_capacity, sizeof(struct http_client));
|
||||||
|
if (!server->clients) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->version.major = 1;
|
||||||
|
server->version.minor = 1;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (server->fd) {
|
||||||
|
close(server->fd);
|
||||||
|
server->fd = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_server_listen(struct http_server *server, uint16_t port) {
|
||||||
|
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (server_fd <= 0) {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int on = 1;
|
||||||
|
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
||||||
|
|
||||||
|
struct sockaddr_in server_addr = { 0 };
|
||||||
|
server_addr.sin_family = AF_INET;
|
||||||
|
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
server_addr.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
|
||||||
|
perror("bind");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(server_fd, 5) == -1) {
|
||||||
|
perror("listen");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->fd = server_fd;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
err:
|
||||||
|
close(server_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_client **result_client, int timeout) {
|
||||||
|
enum poll_result result = POLL_RESULT_ERROR;
|
||||||
|
|
||||||
|
if (server->fd == 0) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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->state == CLIENT_STATE_REQUEST_DONE) {
|
||||||
|
result = POLL_RESULT_REQUEST;
|
||||||
|
client->state = CLIENT_STATE_WAIT_FOR_RESPONSE;
|
||||||
|
(*result_client) = client;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fds_count = 1 + server->clients_len;
|
||||||
|
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;
|
||||||
|
pollfd->events = 0;
|
||||||
|
if (http_client_can_recv(client)) {
|
||||||
|
pollfd->events |= POLLIN;
|
||||||
|
}
|
||||||
|
if (http_client_can_send(client)) {
|
||||||
|
pollfd->events |= POLLOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int poll_result = poll(pollfds, fds_count, timeout);
|
||||||
|
if (poll_result == 0) {
|
||||||
|
log_debug("timeout");
|
||||||
|
result = POLL_RESULT_TIMEOUT;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
if (poll_result < 0) {
|
||||||
|
perror("poll");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pollfd *server_pollfd = &pollfds[0];
|
||||||
|
if (server_pollfd->revents & POLLIN) {
|
||||||
|
struct sockaddr_storage addr = { 0 };
|
||||||
|
socklen_t addr_len = sizeof(addr);
|
||||||
|
|
||||||
|
int client_fd = accept(server->fd, (struct sockaddr *)&addr, &addr_len);
|
||||||
|
if (client_fd == -1) {
|
||||||
|
perror("accept");
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 1; i < fds_count; i++) {
|
||||||
|
struct pollfd *pollfd = &pollfds[i];
|
||||||
|
|
||||||
|
struct http_client *client = &server->clients[i - 1];
|
||||||
|
if (pollfd->revents & POLLIN) {
|
||||||
|
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)) {
|
||||||
|
client->close = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
14
src/utils.c
Normal file
14
src/utils.c
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef UTILS_
|
||||||
|
#define UTILS_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool is_digit(char c) {
|
||||||
|
return '0' <= c && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_alpha(char c) {
|
||||||
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UTILS_
|
Loading…
Reference in New Issue
Block a user