mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
new http request parser
This commit is contained in:
parent
b486bb7970
commit
e15466b460
@ -6,7 +6,6 @@ IndentCaseLabels: true
|
||||
InsertNewlineAtEOF: true
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
BinPackArguments: false
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
|
||||
|
247
backend/src/http/client.c
Normal file
247
backend/src/http/client.c
Normal file
@ -0,0 +1,247 @@
|
||||
#include "client.h"
|
||||
#include "../str_util.h"
|
||||
#include "packet.h"
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline int parse_request_header(Client* client, Request* request);
|
||||
static inline int recieve_request_body(Client* client, Request* request);
|
||||
static inline int next_line(Client* client, StrSlice* slice);
|
||||
static inline int next_char(Client* client, char* ch);
|
||||
static inline int next_u8(Client* client, uint8_t* ch);
|
||||
|
||||
Client* http_client_new(ClientConnection connection)
|
||||
{
|
||||
Client* client = malloc(sizeof(Client) + CLIENT_BUFFER_SIZE);
|
||||
*client = (Client) {
|
||||
.connection = connection,
|
||||
.buffer_i = 0,
|
||||
.buffer_size = 0,
|
||||
};
|
||||
return client;
|
||||
}
|
||||
|
||||
void http_client_free(Client* client)
|
||||
{
|
||||
free(client);
|
||||
}
|
||||
|
||||
int http_client_next(Client* client, Request* request)
|
||||
{
|
||||
if (parse_request_header(client, request) != 0)
|
||||
return -1;
|
||||
|
||||
if (request->method == Method_POST) {
|
||||
if (!http_request_has_header(request, "Content-Length")) {
|
||||
fprintf(stderr,
|
||||
"error: POST request has no body and/or Content-Length "
|
||||
"header\n");
|
||||
return -1;
|
||||
}
|
||||
if (recieve_request_body(client, request) != 0)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int recieve_request_body(Client* client, Request* request)
|
||||
{
|
||||
const char* length_val = http_request_get_header(request, "Content-Length");
|
||||
size_t length = strtoul(length_val, NULL, 10);
|
||||
|
||||
uint8_t* body = calloc(length + 1, sizeof(uint8_t));
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
uint8_t ch;
|
||||
if (next_u8(client, &ch) != 0)
|
||||
return -1;
|
||||
body[i] = ch;
|
||||
}
|
||||
|
||||
request->body = body;
|
||||
request->body_size = length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int parse_request_header(Client* client, Request* request)
|
||||
{
|
||||
|
||||
StrSlice req_line;
|
||||
if (next_line(client, &req_line) != 0)
|
||||
return -1;
|
||||
StrSplitter req_line_splitter
|
||||
= str_splitter(req_line.ptr, req_line.len, " ");
|
||||
StrSlice method_str = str_split_next(&req_line_splitter);
|
||||
StrSlice uri_str = str_split_next(&req_line_splitter);
|
||||
StrSlice version_str = str_split_next(&req_line_splitter);
|
||||
|
||||
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
|
||||
fprintf(stderr,
|
||||
"error: unrecognized http version '%.*s'\n",
|
||||
(int)version_str.len,
|
||||
version_str.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (method_str.len >= 8) {
|
||||
fprintf(stderr, "error: malformed http method\n");
|
||||
return -1;
|
||||
}
|
||||
char normalized_method[8] = "";
|
||||
for (size_t i = 0; i < method_str.len; ++i) {
|
||||
normalized_method[i] = (char)toupper(method_str.ptr[i]);
|
||||
}
|
||||
|
||||
struct MethodStrMapEntry {
|
||||
const char* key;
|
||||
Method val;
|
||||
};
|
||||
const struct MethodStrMapEntry method_str_map[] = {
|
||||
{ "GET", Method_GET },
|
||||
{ "POST", Method_POST },
|
||||
};
|
||||
const size_t method_str_map_size
|
||||
= sizeof(method_str_map) / sizeof(method_str_map[0]);
|
||||
|
||||
Method method;
|
||||
bool method_found = false;
|
||||
for (size_t i = 0; i < method_str_map_size; ++i) {
|
||||
if (strncmp(normalized_method, method_str_map[i].key, method_str.len)
|
||||
== 0) {
|
||||
method = method_str_map[i].val;
|
||||
method_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!method_found) {
|
||||
fprintf(stderr,
|
||||
"error: unrecognized http method '%.*s'\n",
|
||||
(int)method_str.len,
|
||||
method_str.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (uri_str.len >= MAX_PATH_LEN + 1) {
|
||||
fprintf(stderr, "error: path too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t path_len = 0;
|
||||
while (path_len < uri_str.len && uri_str.ptr[path_len] != '?'
|
||||
&& uri_str.ptr[path_len] != '#') {
|
||||
path_len += 1;
|
||||
}
|
||||
|
||||
char* path = calloc(path_len + 1, sizeof(char));
|
||||
strncpy(path, uri_str.ptr, path_len);
|
||||
path[path_len] = '\0';
|
||||
|
||||
char* query = NULL;
|
||||
if (path_len < uri_str.len) {
|
||||
size_t query_len = 0;
|
||||
while (path_len + query_len < uri_str.len
|
||||
&& uri_str.ptr[path_len + query_len] != '#') {
|
||||
query_len += 1;
|
||||
}
|
||||
query = calloc(query_len + 1, sizeof(char));
|
||||
strncpy(query, &uri_str.ptr[path_len], query_len);
|
||||
query[query_len] = '\0';
|
||||
}
|
||||
|
||||
HeaderVec headers;
|
||||
header_vec_construct(&headers);
|
||||
|
||||
while (headers.size < MAX_HEADERS_LEN) {
|
||||
StrSlice line;
|
||||
if (next_line(client, &line) != 0)
|
||||
return -1;
|
||||
if (line.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t key_len = 0;
|
||||
while (key_len < line.len && line.ptr[key_len] != ':') {
|
||||
key_len += 1;
|
||||
}
|
||||
if (key_len == 0 || key_len > MAX_HEADER_KEY_LEN) {
|
||||
fprintf(stderr, "error: header key too long\n");
|
||||
return -1;
|
||||
}
|
||||
size_t value_begin = key_len + 1;
|
||||
while (value_begin < line.len && line.ptr[value_begin] == ' ') {
|
||||
value_begin += 1;
|
||||
}
|
||||
size_t value_len = line.len - value_begin;
|
||||
if (value_len == 0 || value_len > MAX_HEADER_VALUE_LEN) {
|
||||
fprintf(stderr, "error: header value too long, %ld\n", value_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* key = calloc(key_len + 1, sizeof(char));
|
||||
strncpy(key, line.ptr, key_len);
|
||||
|
||||
char* value = calloc(value_len + 1, sizeof(char));
|
||||
strncpy(value, &line.ptr[value_begin], value_len);
|
||||
|
||||
header_vec_push(&headers, (Header) { key, value });
|
||||
}
|
||||
|
||||
*request = (Request) {
|
||||
method,
|
||||
path,
|
||||
query,
|
||||
headers,
|
||||
.body = NULL,
|
||||
.body_size = 0,
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int next_line(Client* client, StrSlice* slice)
|
||||
{
|
||||
size_t begin = client->buffer_i;
|
||||
size_t len = 0;
|
||||
char last = '\0';
|
||||
char ch;
|
||||
if (next_char(client, &ch) != 0)
|
||||
return -1;
|
||||
len += 1;
|
||||
while (!(last == '\r' && ch == '\n')) {
|
||||
last = ch;
|
||||
if (next_char(client, &ch) != 0)
|
||||
return -1;
|
||||
len += 1;
|
||||
}
|
||||
*slice = (StrSlice) {
|
||||
.ptr = (const char*)&client->buffer[begin],
|
||||
.len = len - 2,
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int next_char(Client* client, char* ch)
|
||||
{
|
||||
return next_u8(client, (uint8_t*)ch);
|
||||
}
|
||||
|
||||
static inline int next_u8(Client* client, uint8_t* ch)
|
||||
{
|
||||
if (client->buffer_i >= client->buffer_size) {
|
||||
ssize_t bytes_received = recv(
|
||||
client->connection.file, client->buffer, CLIENT_BUFFER_SIZE, 0);
|
||||
if (bytes_received == -1) {
|
||||
fprintf(stderr,
|
||||
"error: could not receive from client: %s\n",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
client->buffer_i = 0;
|
||||
client->buffer_size = (size_t)bytes_received;
|
||||
}
|
||||
*ch = client->buffer[client->buffer_i++];
|
||||
return 0;
|
||||
}
|
20
backend/src/http/client.h
Normal file
20
backend/src/http/client.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "client_connection.h"
|
||||
#include "request.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define CLIENT_BUFFER_SIZE 8192
|
||||
|
||||
typedef struct {
|
||||
ClientConnection connection;
|
||||
size_t buffer_i;
|
||||
size_t buffer_size;
|
||||
uint8_t buffer[];
|
||||
} Client;
|
||||
|
||||
Client* http_client_new(ClientConnection connection);
|
||||
void http_client_free(Client* client);
|
||||
|
||||
// Returns not 0 on error
|
||||
int http_client_next(Client* client, Request* request);
|
15
backend/src/http/client_connection.h
Normal file
15
backend/src/http/client_connection.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "../collection.h"
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
typedef struct sockaddr SockAddr;
|
||||
typedef struct sockaddr_in SockAddrIn;
|
||||
|
||||
typedef struct {
|
||||
int file;
|
||||
SockAddrIn addr;
|
||||
} ClientConnection;
|
||||
|
||||
DEFINE_STATIC_QUEUE(ClientConnection, ClientQueue, client_queue)
|
24
backend/src/http/packet.h
Normal file
24
backend/src/http/packet.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../collection.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_HEADER_BUFFER_SIZE 65536
|
||||
|
||||
#define MAX_PATH_LEN 128 - 1
|
||||
#define MAX_QUERY_LEN 128 - 1
|
||||
#define MAX_HEADERS_LEN 32
|
||||
#define MAX_HEADER_KEY_LEN 32 - 1
|
||||
#define MAX_HEADER_VALUE_LEN 512 - 1
|
||||
|
||||
typedef enum {
|
||||
Method_GET,
|
||||
Method_POST,
|
||||
} Method;
|
||||
|
||||
typedef struct {
|
||||
char* key;
|
||||
char* value;
|
||||
} Header;
|
||||
|
||||
DEFINE_VEC(Header, HeaderVec, header_vec, 8)
|
178
backend/src/http/request.c
Normal file
178
backend/src/http/request.c
Normal file
@ -0,0 +1,178 @@
|
||||
#include "request.h"
|
||||
#include "../str_util.h"
|
||||
#include "packet.h"
|
||||
#include <ctype.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int http_request_parse(
|
||||
Request* req, size_t* body_idx, const char* const buf, size_t buf_size)
|
||||
{
|
||||
StrSplitter splitter = str_splitter(buf, buf_size, "\r\n");
|
||||
|
||||
StrSlice req_line = str_split_next(&splitter);
|
||||
StrSplitter req_line_splitter
|
||||
= str_splitter(req_line.ptr, req_line.len, " ");
|
||||
StrSlice method_str = str_split_next(&req_line_splitter);
|
||||
StrSlice uri_str = str_split_next(&req_line_splitter);
|
||||
StrSlice version_str = str_split_next(&req_line_splitter);
|
||||
|
||||
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
|
||||
fprintf(stderr,
|
||||
"error: unrecognized http version '%.*s'\n",
|
||||
(int)version_str.len,
|
||||
version_str.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (method_str.len >= 8) {
|
||||
fprintf(stderr, "error: malformed http method\n");
|
||||
return -1;
|
||||
}
|
||||
char normalized_method[8] = "";
|
||||
for (size_t i = 0; i < method_str.len; ++i) {
|
||||
normalized_method[i] = (char)toupper(method_str.ptr[i]);
|
||||
}
|
||||
|
||||
Method method;
|
||||
if (strncmp(normalized_method, "GET", method_str.len) == 0) {
|
||||
method = Method_GET;
|
||||
} else if (strncmp(normalized_method, "POST", method_str.len) == 0) {
|
||||
method = Method_POST;
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"error: unrecognized http method '%.*s'\n",
|
||||
(int)method_str.len,
|
||||
method_str.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (uri_str.len >= MAX_PATH_LEN + 1) {
|
||||
fprintf(stderr, "error: path too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t path_len = 0;
|
||||
while (path_len < uri_str.len && uri_str.ptr[path_len] != '?'
|
||||
&& uri_str.ptr[path_len] != '#') {
|
||||
path_len += 1;
|
||||
}
|
||||
|
||||
char* path = calloc(path_len + 1, sizeof(char));
|
||||
strncpy(path, uri_str.ptr, path_len);
|
||||
path[path_len] = '\0';
|
||||
|
||||
char* query = NULL;
|
||||
if (path_len < uri_str.len) {
|
||||
size_t query_len = 0;
|
||||
while (path_len + query_len < uri_str.len
|
||||
&& uri_str.ptr[path_len + query_len] != '#') {
|
||||
query_len += 1;
|
||||
}
|
||||
query = calloc(query_len + 1, sizeof(char));
|
||||
strncpy(query, &uri_str.ptr[path_len], query_len);
|
||||
query[query_len] = '\0';
|
||||
}
|
||||
|
||||
HeaderVec headers;
|
||||
header_vec_construct(&headers);
|
||||
|
||||
while (headers.size < MAX_HEADERS_LEN) {
|
||||
StrSlice line = str_split_next(&splitter);
|
||||
if (line.len == 0) {
|
||||
*body_idx = (size_t)&line.ptr[2] - (size_t)buf;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t key_len = 0;
|
||||
while (key_len < line.len && line.ptr[key_len] != ':') {
|
||||
key_len += 1;
|
||||
}
|
||||
if (key_len == 0 || key_len > MAX_HEADER_KEY_LEN) {
|
||||
fprintf(stderr, "error: header key too long\n");
|
||||
return -1;
|
||||
}
|
||||
size_t value_begin = key_len + 1;
|
||||
while (value_begin < line.len && line.ptr[value_begin] == ' ') {
|
||||
value_begin += 1;
|
||||
}
|
||||
size_t value_len = line.len - value_begin;
|
||||
if (value_len == 0 || value_len > MAX_HEADER_VALUE_LEN) {
|
||||
fprintf(stderr, "error: header value too long, %ld\n", value_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* key = calloc(key_len + 1, sizeof(char));
|
||||
strncpy(key, line.ptr, key_len);
|
||||
|
||||
char* value = calloc(value_len + 1, sizeof(char));
|
||||
strncpy(value, &line.ptr[value_begin], value_len);
|
||||
|
||||
header_vec_push(&headers, (Header) { key, value });
|
||||
}
|
||||
|
||||
*req = (Request) {
|
||||
method,
|
||||
path,
|
||||
query,
|
||||
headers,
|
||||
.body = NULL,
|
||||
.body_size = 0,
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_request_destroy(Request* req)
|
||||
{
|
||||
free(req->path);
|
||||
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||
free(req->headers.data[i].key);
|
||||
free(req->headers.data[i].value);
|
||||
}
|
||||
header_vec_destroy(&req->headers);
|
||||
if (req->body)
|
||||
free(req->body);
|
||||
}
|
||||
|
||||
static inline int strcmp_lower(const char* a, const char* b)
|
||||
{
|
||||
size_t i = 0;
|
||||
for (; a[i] != '\0' && b[i] != '\0'; ++i) {
|
||||
if (tolower(a[i]) != tolower(b[i])) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (a[i] != b[i]) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool http_request_has_header(const Request* req, const char* key)
|
||||
{
|
||||
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||
if (strcmp_lower(key, req->headers.data[i].key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* http_request_get_header(const Request* req, const char* key)
|
||||
{
|
||||
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||
if (strcmp_lower(key, req->headers.data[i].key) == 0) {
|
||||
return req->headers.data[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
20
backend/src/http/request.h
Normal file
20
backend/src/http/request.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "packet.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
Method method;
|
||||
char* path;
|
||||
char* query;
|
||||
HeaderVec headers;
|
||||
uint8_t* body;
|
||||
size_t body_size;
|
||||
} Request;
|
||||
|
||||
/// On error, returns -1.
|
||||
int http_request_parse(
|
||||
Request* req, size_t* body_idx, const char* const buf, size_t buf_size);
|
||||
void http_request_destroy(Request* req);
|
||||
bool http_request_has_header(const Request* req, const char* key);
|
||||
const char* http_request_get_header(const Request* req, const char* key);
|
@ -1,7 +1,7 @@
|
||||
#include "server.h"
|
||||
#include "../http.h"
|
||||
#include "../str_util.h"
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
@ -17,37 +17,46 @@
|
||||
HttpServer* http_server_new(HttpServerOpts opts)
|
||||
{
|
||||
|
||||
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd == -1) {
|
||||
fprintf(stderr, "error: could not initialize socket\n");
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr,
|
||||
"error: could not initialize socket: %s\n",
|
||||
strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SockAddrIn server_addr;
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(opts.port);
|
||||
SockAddrIn addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(opts.port);
|
||||
|
||||
int reuse = 1;
|
||||
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
|
||||
int res = bind(server_fd, (SockAddr*)&server_addr, sizeof(server_addr));
|
||||
int res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "error: could not bind socket\n");
|
||||
fprintf(stderr,
|
||||
"error: could not set socket options: %s\n",
|
||||
strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = listen(server_fd, 16);
|
||||
res = bind(fd, (SockAddr*)&addr, sizeof(addr));
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "error: could not listen on socket\n");
|
||||
fprintf(stderr, "error: could not bind socket: %s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = listen(fd, 16);
|
||||
if (res != 0) {
|
||||
fprintf(
|
||||
stderr, "error: could not listen on socket: %s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HttpServer* server = malloc(sizeof(HttpServer));
|
||||
*server = (HttpServer) {
|
||||
.file = server_fd,
|
||||
.addr = server_addr,
|
||||
// .ctx = {},
|
||||
.fd = fd,
|
||||
.addr = addr,
|
||||
.ctx = (WorkerCtx) { 0 },
|
||||
.workers = malloc(sizeof(Worker) * opts.workers_amount),
|
||||
.workers_size = opts.workers_amount,
|
||||
.handlers = { 0 },
|
||||
@ -55,9 +64,9 @@ HttpServer* http_server_new(HttpServerOpts opts)
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
|
||||
worker_ctx_construct(&server->ctx, server);
|
||||
http_worker_ctx_construct(&server->ctx, server);
|
||||
for (size_t i = 0; i < opts.workers_amount; ++i) {
|
||||
worker_construct(&server->workers[i], &server->ctx);
|
||||
http_worker_construct(&server->workers[i], &server->ctx);
|
||||
}
|
||||
handler_vec_construct(&server->handlers);
|
||||
|
||||
@ -66,11 +75,11 @@ HttpServer* http_server_new(HttpServerOpts opts)
|
||||
|
||||
void http_server_free(HttpServer* server)
|
||||
{
|
||||
close(server->file);
|
||||
close(server->fd);
|
||||
for (size_t i = 0; i < server->workers_size; ++i) {
|
||||
worker_destroy(&server->workers[i]);
|
||||
http_worker_destroy(&server->workers[i]);
|
||||
}
|
||||
worker_ctx_destroy(&server->ctx);
|
||||
http_worker_ctx_destroy(&server->ctx);
|
||||
handler_vec_destroy(&server->handlers);
|
||||
free(server);
|
||||
}
|
||||
@ -83,13 +92,13 @@ int http_server_listen(HttpServer* server)
|
||||
SockAddrIn client_addr;
|
||||
socklen_t addr_size = sizeof(client_addr);
|
||||
|
||||
int res = accept(server->file, (SockAddr*)&client_addr, &addr_size);
|
||||
int res = accept(server->fd, (SockAddr*)&client_addr, &addr_size);
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "error: could not accept\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Client req = { .file = res, client_addr };
|
||||
ClientConnection req = { .file = res, client_addr };
|
||||
pthread_mutex_lock(&ctx->mutex);
|
||||
|
||||
res = client_queue_push(&ctx->req_queue, req);
|
||||
@ -138,12 +147,12 @@ const char* http_ctx_req_path(HttpCtx* ctx)
|
||||
|
||||
bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key)
|
||||
{
|
||||
return request_has_header(ctx->req, key);
|
||||
return http_request_has_header(ctx->req, key);
|
||||
}
|
||||
|
||||
const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key)
|
||||
{
|
||||
return request_get_header(ctx->req, key);
|
||||
return http_request_get_header(ctx->req, key);
|
||||
}
|
||||
|
||||
const char* http_ctx_req_body(HttpCtx* ctx)
|
||||
@ -170,8 +179,7 @@ void http_ctx_respond(HttpCtx* ctx, int status, const char* body)
|
||||
string_construct(&res);
|
||||
|
||||
char first_line[32];
|
||||
snprintf(
|
||||
first_line,
|
||||
snprintf(first_line,
|
||||
32 - 1,
|
||||
"HTTP/1.1 %d %s\r\n",
|
||||
status,
|
||||
@ -195,347 +203,3 @@ void http_ctx_respond(HttpCtx* ctx, int status, const char* body)
|
||||
|
||||
string_destroy(&res);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
static inline void
|
||||
worker_ctx_construct(WorkerCtx* ctx, const HttpServer* server)
|
||||
{
|
||||
ctx->server = server;
|
||||
pthread_mutex_init(&ctx->mutex, NULL);
|
||||
pthread_cond_init(&ctx->cond, NULL);
|
||||
client_queue_construct(&ctx->req_queue, 8192);
|
||||
}
|
||||
|
||||
static inline void worker_ctx_destroy(WorkerCtx* ctx)
|
||||
{
|
||||
pthread_mutex_destroy(&ctx->mutex);
|
||||
pthread_cond_destroy(&ctx->cond);
|
||||
client_queue_destroy(&ctx->req_queue);
|
||||
}
|
||||
|
||||
static inline int worker_construct(Worker* worker, WorkerCtx* ctx)
|
||||
{
|
||||
*worker = (Worker) {
|
||||
// .thread = {},
|
||||
.ctx = ctx,
|
||||
};
|
||||
|
||||
pthread_create(&worker->thread, NULL, worker_thread_fn, worker);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void worker_destroy(Worker* worker)
|
||||
{
|
||||
if (worker->thread != 0) {
|
||||
pthread_cancel(worker->thread);
|
||||
|
||||
// a bit ugly, but who cares?
|
||||
pthread_cond_broadcast(&worker->ctx->cond);
|
||||
|
||||
pthread_join(worker->thread, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void* worker_thread_fn(void* data)
|
||||
{
|
||||
Worker* worker = data;
|
||||
worker_listen(worker);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void worker_listen(Worker* worker)
|
||||
{
|
||||
WorkerCtx* ctx = worker->ctx;
|
||||
while (true) {
|
||||
pthread_testcancel();
|
||||
|
||||
pthread_mutex_lock(&ctx->mutex);
|
||||
|
||||
if (client_queue_size(&ctx->req_queue) > 0) {
|
||||
Client req;
|
||||
client_queue_pop(&ctx->req_queue, &req);
|
||||
pthread_mutex_unlock(&ctx->mutex);
|
||||
|
||||
worker_handle_request(worker, &req);
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_cond_wait(&ctx->cond, &ctx->mutex);
|
||||
|
||||
pthread_mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void worker_handle_request(Worker* worker, Client* client)
|
||||
{
|
||||
(void)worker;
|
||||
|
||||
uint8_t* buffer = calloc(MAX_HEADER_BUFFER_SIZE, sizeof(char));
|
||||
ssize_t bytes_received
|
||||
= recv(client->file, buffer, MAX_HEADER_BUFFER_SIZE * sizeof(char), 0);
|
||||
|
||||
if (bytes_received == -1) {
|
||||
fprintf(stderr, "error: could not receive request\n");
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
size_t header_end = 0;
|
||||
for (ssize_t i = 0; i < bytes_received - 3; ++i) {
|
||||
if (memcmp((char*)&buffer[i], "\r\n\r\n", 4)) {
|
||||
header_end = (size_t)i + 5;
|
||||
}
|
||||
}
|
||||
if (header_end == 0) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: header too big, exceeded %d bytes\n",
|
||||
MAX_HEADER_BUFFER_SIZE);
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
Request req;
|
||||
size_t body_idx;
|
||||
int res = parse_request_header(&req, &body_idx, (char*)buffer, header_end);
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "error: failed to parse header\n");
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
char* body = NULL;
|
||||
if (req.method == Method_POST) {
|
||||
if (!request_has_header(&req, "Content-Length")) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: POST request has no body and/or Content-Length "
|
||||
"header\n");
|
||||
goto l1_return;
|
||||
}
|
||||
const char* length_val = request_get_header(&req, "Content-Length");
|
||||
size_t length = strtoul(length_val, NULL, 10);
|
||||
body = calloc(length + 1, sizeof(char));
|
||||
strncpy(body, (char*)&buffer[body_idx], length);
|
||||
body[length] = '\0';
|
||||
|
||||
// HACK
|
||||
// TODO: We should treat the input as a stream rather than a block.
|
||||
// Look at either @camper0008's stream example or other HTTP-server
|
||||
// implementations.
|
||||
size_t defacto_length = strlen(body);
|
||||
int attempts = 0;
|
||||
const int arbitrary_max_attempts = 10;
|
||||
while (defacto_length < length && attempts < arbitrary_max_attempts) {
|
||||
attempts += 1;
|
||||
|
||||
uint8_t buffer[8192];
|
||||
ssize_t recieved = recv(client->file, buffer, 8192, 0);
|
||||
|
||||
if (recieved == -1) {
|
||||
fprintf(stderr, "error: could not receive request body\n");
|
||||
goto l1_return;
|
||||
}
|
||||
|
||||
strncpy(&body[defacto_length], (char*)buffer, (size_t)recieved);
|
||||
defacto_length += (size_t)recieved;
|
||||
body[defacto_length] = '\0';
|
||||
}
|
||||
if (defacto_length < length) {
|
||||
fprintf(stderr, "error: could not receive entire request body\n");
|
||||
goto l1_return;
|
||||
}
|
||||
}
|
||||
|
||||
HttpCtx handler_ctx = {
|
||||
.client = client,
|
||||
.req = &req,
|
||||
.req_body = body,
|
||||
.res_headers = { 0 },
|
||||
.user_ctx = worker->ctx->server->user_ctx,
|
||||
};
|
||||
|
||||
header_vec_construct(&handler_ctx.res_headers);
|
||||
|
||||
bool been_handled = false;
|
||||
|
||||
for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) {
|
||||
Handler* handler = &worker->ctx->server->handlers.data[i];
|
||||
if (handler->method != req.method)
|
||||
continue;
|
||||
if (strcmp(handler->path, req.path) != 0)
|
||||
continue;
|
||||
handler->handler(&handler_ctx);
|
||||
been_handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!been_handled && worker->ctx->server->not_found_handler != NULL) {
|
||||
worker->ctx->server->not_found_handler(&handler_ctx);
|
||||
}
|
||||
|
||||
l1_return:
|
||||
header_vec_destroy(&handler_ctx.res_headers);
|
||||
request_destroy(&req);
|
||||
if (body)
|
||||
free(body);
|
||||
l0_return:
|
||||
free(buffer);
|
||||
close(client->file);
|
||||
}
|
||||
|
||||
static inline int parse_request_header(
|
||||
Request* req, size_t* body_idx, const char* const buf, size_t buf_size)
|
||||
{
|
||||
StrSplitter splitter = str_split(buf, buf_size, "\r\n");
|
||||
|
||||
StrSlice req_line = strsplit_next(&splitter);
|
||||
StrSplitter req_line_splitter = str_split(req_line.ptr, req_line.len, " ");
|
||||
StrSlice method_str = strsplit_next(&req_line_splitter);
|
||||
StrSlice uri_str = strsplit_next(&req_line_splitter);
|
||||
StrSlice version_str = strsplit_next(&req_line_splitter);
|
||||
|
||||
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: unrecognized http version '%.*s'\n",
|
||||
(int)version_str.len,
|
||||
version_str.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (method_str.len >= 8) {
|
||||
fprintf(stderr, "error: malformed http method\n");
|
||||
return -1;
|
||||
}
|
||||
char normalized_method[8] = "";
|
||||
for (size_t i = 0; i < method_str.len; ++i) {
|
||||
normalized_method[i] = (char)toupper(method_str.ptr[i]);
|
||||
}
|
||||
|
||||
Method method;
|
||||
if (strncmp(normalized_method, "GET", method_str.len) == 0) {
|
||||
method = Method_GET;
|
||||
} else if (strncmp(normalized_method, "POST", method_str.len) == 0) {
|
||||
method = Method_POST;
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
"error: unrecognized http method '%.*s'\n",
|
||||
(int)method_str.len,
|
||||
method_str.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (uri_str.len >= MAX_PATH_LEN + 1) {
|
||||
fprintf(stderr, "error: path too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t path_len = 0;
|
||||
while (path_len < uri_str.len && uri_str.ptr[path_len] != '?'
|
||||
&& uri_str.ptr[path_len] != '#') {
|
||||
path_len += 1;
|
||||
}
|
||||
|
||||
char* path = calloc(path_len + 1, sizeof(char));
|
||||
strncpy(path, uri_str.ptr, path_len);
|
||||
path[path_len] = '\0';
|
||||
|
||||
char* query = NULL;
|
||||
if (path_len < uri_str.len) {
|
||||
size_t query_len = 0;
|
||||
while (path_len + query_len < uri_str.len
|
||||
&& uri_str.ptr[path_len + query_len] != '#') {
|
||||
query_len += 1;
|
||||
}
|
||||
query = calloc(query_len + 1, sizeof(char));
|
||||
strncpy(query, &uri_str.ptr[path_len], query_len);
|
||||
query[query_len] = '\0';
|
||||
}
|
||||
|
||||
HeaderVec headers;
|
||||
header_vec_construct(&headers);
|
||||
|
||||
while (headers.size < MAX_HEADERS_LEN) {
|
||||
StrSlice line = strsplit_next(&splitter);
|
||||
if (line.len == 0) {
|
||||
*body_idx = (size_t)&line.ptr[2] - (size_t)buf;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t key_len = 0;
|
||||
while (key_len < line.len && line.ptr[key_len] != ':') {
|
||||
key_len += 1;
|
||||
}
|
||||
if (key_len == 0 || key_len > MAX_HEADER_KEY_LEN) {
|
||||
fprintf(stderr, "error: header key too long\n");
|
||||
return -1;
|
||||
}
|
||||
size_t value_begin = key_len + 1;
|
||||
while (value_begin < line.len && line.ptr[value_begin] == ' ') {
|
||||
value_begin += 1;
|
||||
}
|
||||
size_t value_len = line.len - value_begin;
|
||||
if (value_len == 0 || value_len > MAX_HEADER_VALUE_LEN) {
|
||||
fprintf(stderr, "error: header value too long, %ld\n", value_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* key = calloc(key_len + 1, sizeof(char));
|
||||
strncpy(key, line.ptr, key_len);
|
||||
|
||||
char* value = calloc(value_len + 1, sizeof(char));
|
||||
strncpy(value, &line.ptr[value_begin], value_len);
|
||||
|
||||
header_vec_push(&headers, (Header) { key, value });
|
||||
}
|
||||
|
||||
*req = (Request) { method, path, query, headers };
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void request_destroy(Request* req)
|
||||
{
|
||||
free(req->path);
|
||||
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||
free(req->headers.data[i].key);
|
||||
free(req->headers.data[i].value);
|
||||
}
|
||||
header_vec_destroy(&req->headers);
|
||||
}
|
||||
|
||||
static inline int strcmp_lower(const char* a, const char* b)
|
||||
{
|
||||
size_t i = 0;
|
||||
for (; a[i] != '\0' && b[i] != '\0'; ++i) {
|
||||
if (tolower(a[i]) != tolower(b[i])) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (a[i] != b[i]) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool request_has_header(const Request* req, const char* key)
|
||||
{
|
||||
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||
if (strcmp_lower(key, req->headers.data[i].key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
request_get_header(const Request* req, const char* key)
|
||||
{
|
||||
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||
if (strcmp_lower(key, req->headers.data[i].key) == 0) {
|
||||
return req->headers.data[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
#include "../collection.h"
|
||||
#include "../http.h"
|
||||
#include "client_connection.h"
|
||||
#include "packet.h"
|
||||
#include "request.h"
|
||||
#include "worker.h"
|
||||
#include <bits/pthreadtypes.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
@ -11,75 +15,6 @@
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct sockaddr SockAddr;
|
||||
typedef struct sockaddr_in SockAddrIn;
|
||||
|
||||
typedef struct {
|
||||
int file;
|
||||
SockAddrIn addr;
|
||||
} Client;
|
||||
|
||||
DEFINE_STATIC_QUEUE(Client, ClientQueue, client_queue)
|
||||
|
||||
typedef struct {
|
||||
const HttpServer* server;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
ClientQueue req_queue;
|
||||
} WorkerCtx;
|
||||
|
||||
static inline void
|
||||
worker_ctx_construct(WorkerCtx* ctx, const HttpServer* server);
|
||||
static inline void worker_ctx_destroy(WorkerCtx* ctx);
|
||||
|
||||
typedef struct {
|
||||
pthread_t thread;
|
||||
WorkerCtx* ctx;
|
||||
} Worker;
|
||||
|
||||
/// On ok, returns 0.
|
||||
/// On error, returns -1;
|
||||
static inline int worker_construct(Worker* worker, WorkerCtx* ctx);
|
||||
static inline void worker_destroy(Worker* worker);
|
||||
static inline void* worker_thread_fn(void* data);
|
||||
static inline void worker_listen(Worker* worker);
|
||||
static inline void worker_handle_request(Worker* worker, Client* req);
|
||||
|
||||
#define MAX_HEADER_BUFFER_SIZE 65536
|
||||
|
||||
#define MAX_PATH_LEN 128 - 1
|
||||
#define MAX_QUERY_LEN 128 - 1
|
||||
#define MAX_HEADERS_LEN 32
|
||||
#define MAX_HEADER_KEY_LEN 32 - 1
|
||||
#define MAX_HEADER_VALUE_LEN 512 - 1
|
||||
|
||||
typedef enum {
|
||||
Method_GET,
|
||||
Method_POST,
|
||||
} Method;
|
||||
|
||||
typedef struct {
|
||||
char* key;
|
||||
char* value;
|
||||
} Header;
|
||||
|
||||
DEFINE_VEC(Header, HeaderVec, header_vec, 8)
|
||||
|
||||
typedef struct {
|
||||
Method method;
|
||||
char* path;
|
||||
char* query;
|
||||
HeaderVec headers;
|
||||
} Request;
|
||||
|
||||
/// On error, returns -1.
|
||||
static inline int parse_request_header(
|
||||
Request* req, size_t* body_idx, const char* const buf, size_t buf_size);
|
||||
static inline void request_destroy(Request* req);
|
||||
static inline bool request_has_header(const Request* req, const char* key);
|
||||
static inline const char*
|
||||
request_get_header(const Request* req, const char* key);
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
Method method;
|
||||
@ -89,7 +24,7 @@ typedef struct {
|
||||
DEFINE_VEC(Handler, HandlerVec, handler_vec, 8)
|
||||
|
||||
struct HttpServer {
|
||||
int file;
|
||||
int fd;
|
||||
SockAddrIn addr;
|
||||
WorkerCtx ctx;
|
||||
Worker* workers;
|
||||
@ -100,7 +35,7 @@ struct HttpServer {
|
||||
};
|
||||
|
||||
struct HttpCtx {
|
||||
Client* client;
|
||||
ClientConnection* client;
|
||||
const Request* req;
|
||||
const char* req_body;
|
||||
HeaderVec res_headers;
|
||||
|
126
backend/src/http/worker.c
Normal file
126
backend/src/http/worker.c
Normal file
@ -0,0 +1,126 @@
|
||||
#include "worker.h"
|
||||
#include "client.h"
|
||||
#include "server.h"
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
void http_worker_ctx_construct(WorkerCtx* ctx, const HttpServer* server)
|
||||
{
|
||||
ctx->server = server;
|
||||
pthread_mutex_init(&ctx->mutex, NULL);
|
||||
pthread_cond_init(&ctx->cond, NULL);
|
||||
client_queue_construct(&ctx->req_queue, 8192);
|
||||
}
|
||||
|
||||
void http_worker_ctx_destroy(WorkerCtx* ctx)
|
||||
{
|
||||
pthread_mutex_destroy(&ctx->mutex);
|
||||
pthread_cond_destroy(&ctx->cond);
|
||||
client_queue_destroy(&ctx->req_queue);
|
||||
}
|
||||
|
||||
void http_worker_construct(Worker* worker, WorkerCtx* ctx)
|
||||
{
|
||||
*worker = (Worker) {
|
||||
.thread = (pthread_t) { 0 },
|
||||
.ctx = ctx,
|
||||
};
|
||||
|
||||
pthread_create(&worker->thread, NULL, http_worker_thread_fn, worker);
|
||||
}
|
||||
|
||||
void http_worker_destroy(Worker* worker)
|
||||
{
|
||||
if (worker->thread != 0) {
|
||||
pthread_cancel(worker->thread);
|
||||
|
||||
// a bit ugly, but who cares?
|
||||
pthread_cond_broadcast(&worker->ctx->cond);
|
||||
|
||||
pthread_join(worker->thread, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void* http_worker_thread_fn(void* data)
|
||||
{
|
||||
Worker* worker = data;
|
||||
http_worker_listen(worker);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void http_worker_listen(Worker* worker)
|
||||
{
|
||||
WorkerCtx* ctx = worker->ctx;
|
||||
while (true) {
|
||||
pthread_testcancel();
|
||||
|
||||
pthread_mutex_lock(&ctx->mutex);
|
||||
|
||||
if (client_queue_size(&ctx->req_queue) > 0) {
|
||||
ClientConnection connection;
|
||||
client_queue_pop(&ctx->req_queue, &connection);
|
||||
pthread_mutex_unlock(&ctx->mutex);
|
||||
|
||||
http_worker_handle_connection(worker, connection);
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_cond_wait(&ctx->cond, &ctx->mutex);
|
||||
|
||||
pthread_mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void http_worker_handle_connection(Worker* worker, ClientConnection connection)
|
||||
{
|
||||
(void)worker;
|
||||
|
||||
Client* client = http_client_new(connection);
|
||||
Request request;
|
||||
|
||||
int res = http_client_next(client, &request);
|
||||
if (res != 0) {
|
||||
fprintf(stderr,
|
||||
"warning: failed to parse request. sending 400 Bad Request "
|
||||
"response\n");
|
||||
const char* res = "HTTP/1.1 400 Bad Request\r\n\r\n";
|
||||
ssize_t bytes_written = write(connection.file, res, strlen(res));
|
||||
if (bytes_written != (ssize_t)strlen(res)) {
|
||||
fprintf(stderr, "error: could not send 400 Bad Request response\n");
|
||||
}
|
||||
goto l0_return;
|
||||
}
|
||||
|
||||
HttpCtx handler_ctx = {
|
||||
.client = &client->connection,
|
||||
.req = &request,
|
||||
.req_body = (char*)request.body,
|
||||
.res_headers = { 0 },
|
||||
.user_ctx = worker->ctx->server->user_ctx,
|
||||
};
|
||||
|
||||
header_vec_construct(&handler_ctx.res_headers);
|
||||
|
||||
bool been_handled = false;
|
||||
|
||||
for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) {
|
||||
Handler* handler = &worker->ctx->server->handlers.data[i];
|
||||
if (handler->method != request.method)
|
||||
continue;
|
||||
if (strcmp(handler->path, request.path) != 0)
|
||||
continue;
|
||||
handler->handler(&handler_ctx);
|
||||
been_handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!been_handled && worker->ctx->server->not_found_handler != NULL) {
|
||||
worker->ctx->server->not_found_handler(&handler_ctx);
|
||||
}
|
||||
|
||||
header_vec_destroy(&handler_ctx.res_headers);
|
||||
http_request_destroy(&request);
|
||||
l0_return:
|
||||
close(client->connection.file);
|
||||
http_client_free(client);
|
||||
}
|
26
backend/src/http/worker.h
Normal file
26
backend/src/http/worker.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "../http.h"
|
||||
#include "client_connection.h"
|
||||
#include <bits/pthreadtypes.h>
|
||||
|
||||
typedef struct {
|
||||
const HttpServer* server;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
ClientQueue req_queue;
|
||||
} WorkerCtx;
|
||||
|
||||
void http_worker_ctx_construct(WorkerCtx* ctx, const HttpServer* server);
|
||||
void http_worker_ctx_destroy(WorkerCtx* ctx);
|
||||
|
||||
typedef struct {
|
||||
pthread_t thread;
|
||||
WorkerCtx* ctx;
|
||||
} Worker;
|
||||
|
||||
void http_worker_construct(Worker* worker, WorkerCtx* ctx);
|
||||
void http_worker_destroy(Worker* worker);
|
||||
void* http_worker_thread_fn(void* data);
|
||||
void http_worker_listen(Worker* worker);
|
||||
void http_worker_handle_connection(Worker* worker, ClientConnection connection);
|
@ -14,7 +14,7 @@ char* str_dup(const char* str)
|
||||
return clone;
|
||||
}
|
||||
|
||||
StrSplitter str_split(const char* text, size_t text_len, const char* split)
|
||||
StrSplitter str_splitter(const char* text, size_t text_len, const char* split)
|
||||
{
|
||||
return (StrSplitter) {
|
||||
.text = text,
|
||||
@ -25,7 +25,7 @@ StrSplitter str_split(const char* text, size_t text_len, const char* split)
|
||||
};
|
||||
}
|
||||
|
||||
StrSlice strsplit_next(StrSplitter* splitter)
|
||||
StrSlice str_split_next(StrSplitter* splitter)
|
||||
{
|
||||
for (size_t i = splitter->i; i < splitter->text_len; ++i) {
|
||||
if (strncmp(&splitter->text[i], splitter->split, splitter->split_len)
|
||||
|
@ -20,8 +20,8 @@ typedef struct {
|
||||
size_t split_len;
|
||||
} StrSplitter;
|
||||
|
||||
StrSplitter str_split(const char* text, size_t text_len, const char* split);
|
||||
StrSlice strsplit_next(StrSplitter* splitter);
|
||||
StrSplitter str_splitter(const char* text, size_t text_len, const char* split);
|
||||
StrSlice str_split_next(StrSplitter* splitter);
|
||||
|
||||
DEFINE_VEC(char, String, string, 8)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user