feat: response bodies from char buffers; generated responses

c-web-server
Jef Roosens 2023-05-29 11:36:57 +02:00
parent 62348c14f5
commit 2fb3e2bb00
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
12 changed files with 163 additions and 108 deletions

View File

@ -53,6 +53,8 @@ typedef struct event_loop {
void (*ctx_free)(void *ctx); void (*ctx_free)(void *ctx);
// Function that processes incoming data // Function that processes incoming data
bool (*handle_data)(event_loop_conn *conn); bool (*handle_data)(event_loop_conn *conn);
// Function that writes outgoing data
void (*write_data)(event_loop_conn *conn);
} event_loop; } event_loop;
/* /*

View File

@ -1,20 +1,13 @@
#ifndef HTTP #ifndef HTTP
#define HTTP #define HTTP
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include "picohttpparser.h" #include "picohttpparser.h"
#define HTTP_MAX_ALLOWED_HEADERS 8 #define HTTP_MAX_ALLOWED_HEADERS 16
extern const char http_404[];
extern const size_t http_404_len;
extern const char http_405[];
extern const size_t http_405_len;
extern const char http_500[];
extern const size_t http_500_len;
extern const char http_501[];
extern const size_t http_501_len;
extern const char *http_response_type_names[5][32]; extern const char *http_response_type_names[5][32];
typedef enum http_request_method { typedef enum http_request_method {
@ -120,9 +113,14 @@ typedef enum http_response_type {
typedef struct http_response { typedef struct http_response {
http_response_type type; http_response_type type;
const char *head;
size_t head_len;
size_t head_written;
const char *body; const char *body;
size_t body_len; size_t body_len;
size_t body_written; size_t body_written;
// If false, the body won't be freed
bool owns_body;
} http_response; } http_response;
#endif #endif

View File

@ -39,7 +39,6 @@ http_loop_gctx *http_loop_gctx_init();
typedef struct http_loop_ctx { typedef struct http_loop_ctx {
http_request req; http_request req;
http_response res; http_response res;
bool flush;
http_route *route; http_route *route;
size_t current_step; size_t current_step;
http_loop_gctx *g; http_loop_gctx *g;
@ -62,12 +61,18 @@ void http_loop_ctx_free(http_loop_ctx *ctx);
*/ */
bool http_loop_handle_request(event_loop_conn *conn); bool http_loop_handle_request(event_loop_conn *conn);
void http_loop_write_response(event_loop_conn *conn);
http_parse_error http_loop_parse_request(event_loop_conn *conn); http_parse_error http_loop_parse_request(event_loop_conn *conn);
bool http_loop_route_request(event_loop_conn *conn); void http_loop_route_request(event_loop_conn *conn);
void http_loop_process_request(event_loop_conn *conn); void http_loop_process_request(event_loop_conn *conn);
void http_loop_res_set_body(const char *body, size_t body_len);
void http_loop_res_set_type(http_response_type type);
void http_loop_write_standard_response(event_loop_conn *conn, void http_loop_write_standard_response(event_loop_conn *conn,
http_response_type type); http_response_type type);

View File

@ -4,8 +4,8 @@
#include "event_loop.h" #include "event_loop.h"
void event_loop_conn_io_res(event_loop_conn *conn) { void event_loop_conn_io_res(event_loop *el, event_loop_conn *conn) {
while (1) { do {
ssize_t res = 0; ssize_t res = 0;
size_t remain = conn->wbuf_size - conn->wbuf_sent; size_t remain = conn->wbuf_size - conn->wbuf_sent;
@ -28,17 +28,15 @@ void event_loop_conn_io_res(event_loop_conn *conn) {
conn->wbuf_sent += (size_t)res; conn->wbuf_sent += (size_t)res;
// After writing a response, we switch back to request mode to process a // After writing the entire buffer, we run the write_data command to receive
// next request // new data, or exit the loop
if (conn->wbuf_sent == conn->wbuf_size) { if (conn->wbuf_sent == conn->wbuf_size) {
conn->state = conn->close_after_write ? event_loop_conn_state_end conn->wbuf_sent = 0;
: event_loop_conn_state_req; conn->wbuf_size = 0;
/* c->wbuf_sent = 0; */
/* c->wbuf_size = 0; */
return; el->write_data(conn);
}
} }
} while (conn->state == event_loop_conn_state_res);
} }
/** /**
@ -93,7 +91,7 @@ void event_loop_conn_io(event_loop *el, event_loop_conn *conn) {
event_loop_conn_io_req(el, conn); event_loop_conn_io_req(el, conn);
break; break;
case event_loop_conn_state_res: case event_loop_conn_state_res:
event_loop_conn_io_res(conn); event_loop_conn_io_res(el, conn);
break; break;
case event_loop_conn_state_end:; case event_loop_conn_state_end:;
} }

6
src/http/http.c 100644
View File

@ -0,0 +1,6 @@
#include "http.h"
// Very important that this is in the same order as http_request_method
const char *request_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"};
const size_t request_method_names_len =
sizeof(request_method_names) / sizeof(request_method_names[0]);

View File

@ -21,22 +21,22 @@ bool http_loop_handle_request(event_loop_conn *conn) {
return false; return false;
} }
// It's fun to respond with extremely specific error messages
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501
else if (res == http_parse_error_unknown_method) {
http_loop_write_standard_response(conn, 501);
return false;
}
conn->rbuf_read += res; conn->rbuf_read += res;
// If the routing fails, we exit the handler // It's fun to respond with extremely specific error messages
if (!http_loop_route_request(conn)) { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501
return false; if (res == http_parse_error_unknown_method) {
ctx->res.type = http_method_not_implemented;
conn->state = event_loop_conn_state_res;
} else {
http_loop_route_request(conn);
} }
} }
if (conn->state == event_loop_conn_state_req) {
http_loop_process_request(conn); http_loop_process_request(conn);
}
// TODO in highly concurrent situations, it might actually be better to always // TODO in highly concurrent situations, it might actually be better to always
// return false here, as this allows cycling better through all connections // return false here, as this allows cycling better through all connections
@ -49,6 +49,7 @@ event_loop *http_loop_init(http_loop_gctx *gctx) {
el->ctx_init = (void *(*)(void *))http_loop_ctx_init; el->ctx_init = (void *(*)(void *))http_loop_ctx_init;
el->ctx_free = (void (*)(void *))http_loop_ctx_free; el->ctx_free = (void (*)(void *))http_loop_ctx_free;
el->handle_data = http_loop_handle_request; el->handle_data = http_loop_handle_request;
el->write_data = http_loop_write_response;
el->gctx = gctx; el->gctx = gctx;
return el; return el;

View File

@ -1,21 +0,0 @@
#include "http.h"
const char http_404[] = "HTTP/1.1 404 Not Found\n"
"Content-Length: 0\n\n";
const size_t http_404_len = sizeof(http_404) - 1;
const char http_405[] = "HTTP/1.1 405 Method Not Allowed\n"
"Content-Length: 0\n\n";
const size_t http_405_len = sizeof(http_405) - 1;
const char http_500[] = "HTTP/1.1 500 Internal Server Error\n"
"Content-Length: 0\n\n";
const size_t http_500_len = sizeof(http_500) - 1;
const char http_501[] = "HTTP/1.1 501 Not Implemented\n"
"Content-Length: 0\n\n";
const size_t http_501_len = sizeof(http_501) - 1;
// Very important that this is in the same order as http_request_method
const char *request_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"};
const size_t request_method_names_len =
sizeof(request_method_names) / sizeof(request_method_names[0]);

View File

@ -14,10 +14,30 @@ http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) {
return ctx; return ctx;
} }
void http_loop_ctx_free(http_loop_ctx *ctx) { free(ctx); } void http_loop_ctx_free(http_loop_ctx *ctx) {
http_loop_ctx_reset(ctx);
free(ctx);
}
void http_loop_ctx_reset(http_loop_ctx *ctx) { void http_loop_ctx_reset(http_loop_ctx *ctx) {
ctx->route = NULL; ctx->route = NULL;
ctx->current_step = 0; ctx->current_step = 0;
ctx->flush = false;
if (ctx->res.head != NULL) {
free((void *)ctx->res.head);
ctx->res.head = NULL;
}
if (ctx->res.owns_body && ctx->res.body != NULL) {
free((void *)ctx->res.body);
}
ctx->res.body = NULL;
ctx->res.head_len = 0;
ctx->res.head_written = 0;
ctx->res.body_len = 0;
ctx->res.body_written = 0;
ctx->res.owns_body = false;
} }

View File

@ -71,24 +71,7 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) {
return http_parse_error_ok; return http_parse_error_ok;
} }
void http_loop_process_request(event_loop_conn *conn) { void http_loop_route_request(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
// We keep processing step functions as long as they don't need to wait for
// I/O
while ((ctx->route->steps[ctx->current_step] != NULL) &&
ctx->route->steps[ctx->current_step](conn)) {
ctx->current_step++;
}
// If we've reached the end of the list of step functions, we report the
// request as finished by clearing its route
if (ctx->route->steps[ctx->current_step] == NULL) {
http_loop_ctx_reset(ctx);
}
}
bool http_loop_route_request(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx; http_loop_ctx *ctx = conn->ctx;
http_loop_gctx *gctx = ctx->g; http_loop_gctx *gctx = ctx->g;
@ -104,7 +87,7 @@ bool http_loop_route_request(event_loop_conn *conn) {
case http_route_literal: case http_route_literal:
if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) { if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) {
ctx->route = route; ctx->route = route;
return true; return;
} }
// TODO // TODO
case http_route_regex:; case http_route_regex:;
@ -112,7 +95,28 @@ bool http_loop_route_request(event_loop_conn *conn) {
} }
// Fallthrough is to write a 404 // Fallthrough is to write a 404
http_loop_write_standard_response(conn, http_not_found); ctx->res.type = http_not_found;
conn->state = event_loop_conn_state_res;
return false; }
void http_loop_process_request(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
// We keep processing step functions as long as they don't need to wait for
// I/O
while ((conn->state == event_loop_conn_state_req) &&
(ctx->route->steps[ctx->current_step] != NULL) &&
ctx->route->steps[ctx->current_step](conn)) {
ctx->current_step++;
}
if (conn->state != event_loop_conn_state_req) {
return;
}
// If we've reached the end of the list of step functions, we report the
// request as finished by clearing its route
if (ctx->route->steps[ctx->current_step] == NULL) {
http_loop_ctx_reset(ctx);
}
} }

View File

@ -0,0 +1,58 @@
#include <stdio.h>
#include "http_loop.h"
#include "log.h"
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
void http_loop_write_response(event_loop_conn *conn) {
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
// Create head response
if (res->head == NULL) {
const char *response_type_name =
http_response_type_names[res->type / 100 - 1][res->type % 100];
char *format = "HTTP/1.1 %i %s\n"
"Content-Length: %lu\n\n";
// Running snprintf with size 0 prevents it from writing any bytes, while
// still letting it calculate how many bytes it would have written
int buf_size =
snprintf(NULL, 0, format, res->type, response_type_name, res->body_len);
char *buf = malloc(buf_size + 1);
sprintf(buf, format, res->type, response_type_name, res->body_len);
res->head = buf;
res->head_len = buf_size;
}
// The final iteration marks the end of the response, after which we reset the
// context so a next request can be processed
if (res->head_written == res->head_len &&
res->body_written == res->body_len) {
http_loop_ctx_reset(conn->ctx);
conn->state = event_loop_conn_state_req;
return;
}
if (res->head_written < res->head_len) {
size_t bytes_to_write = MIN(res->head_len - res->head_written,
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written],
bytes_to_write);
conn->wbuf_size += bytes_to_write;
res->head_written += bytes_to_write;
}
if (res->body_written < res->body_len) {
size_t bytes_to_write = MIN(res->body_len - res->body_written,
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
memcpy(&conn->wbuf[conn->wbuf_size], &res->body[res->body_written],
bytes_to_write);
conn->wbuf_size += bytes_to_write;
res->body_written += bytes_to_write;
}
}

View File

@ -1,27 +0,0 @@
#include "http_loop.h"
#include "string.h"
void http_loop_write_standard_response(event_loop_conn *conn,
http_response_type type) {
const char *s;
size_t len;
switch (type) {
case 404:
s = http_404;
len = http_404_len;
break;
case 405:
s = http_405;
len = http_405_len;
case 501:
s = http_501;
len = http_501_len;
}
memcpy(conn->wbuf, s, len);
conn->state = event_loop_conn_state_res;
conn->wbuf_size = len;
conn->wbuf_sent = 0;
}

View File

@ -4,12 +4,23 @@
#include "http_loop.h" #include "http_loop.h"
#include "log.h" #include "log.h"
bool http_write_404(event_loop_conn *conn) { const char index_page[] =
memcpy(conn->wbuf, http_405, http_405_len); "<!DOCTYPE html>\n"
"<html>\n"
" <body>\n"
" <h1>r8r.be</h1>\n"
" <p>This is the URL shortener and pastebin accompanying my site, <a "
"href=\"https://rustybever.be\">The Rusty Bever</a>.</p>\n"
" </body>\n"
"</html>\n";
bool http_write_404(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
ctx->res.type = 200;
ctx->res.body = index_page;
ctx->res.body_len = sizeof(index_page) - 1;
ctx->res.owns_body = false;
conn->state = event_loop_conn_state_res; conn->state = event_loop_conn_state_res;
conn->wbuf_size = http_405_len;
conn->wbuf_sent = 0;
return true; return true;
} }