feat: response bodies from char buffers; generated responses
parent
62348c14f5
commit
2fb3e2bb00
|
@ -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;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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:;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http_loop_process_request(conn);
|
if (conn->state == event_loop_conn_state_req) {
|
||||||
|
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;
|
||||||
|
|
|
@ -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]);
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
19
src/main.c
19
src/main.c
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue