feat(http): add custom processing to responses using response steps
parent
6d74c8c550
commit
afd18d3a37
|
@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased](https://git.rustybever.be/Chewing_Bever/lander/src/branch/dev)
|
## [Unreleased](https://git.rustybever.be/Chewing_Bever/lander/src/branch/dev)
|
||||||
|
|
||||||
* Fully decoupled HTTP loop functionnality
|
* HTTP Loop
|
||||||
|
* Fully decoupled functionality from Lander-specific code
|
||||||
|
* Users can now define custom global & request-local contexts
|
||||||
|
* Introduced "response steps", allowing custom code during the response
|
||||||
|
part of a request
|
||||||
|
|
||||||
## [0.1.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.1.0)
|
## [0.1.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.1.0)
|
||||||
|
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -43,7 +43,7 @@ libtrie:
|
||||||
liblsm:
|
liblsm:
|
||||||
$(MAKE) -C lsm
|
$(MAKE) -C lsm
|
||||||
|
|
||||||
.PHONY: bin
|
.PHONY: $(BIN)
|
||||||
$(BIN): libtrie liblsm $(OBJS)
|
$(BIN): libtrie liblsm $(OBJS)
|
||||||
$(CC) -o $@ $(OBJS) $(_LDFLAGS)
|
$(CC) -o $@ $(OBJS) $(_LDFLAGS)
|
||||||
|
|
||||||
|
@ -58,11 +58,17 @@ $(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c
|
||||||
|
|
||||||
# =====TESTING=====
|
# =====TESTING=====
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: bin
|
run: $(BIN)
|
||||||
LANDER_API_KEY=test \
|
LANDER_API_KEY=test \
|
||||||
LANDER_DATA_DIR=data \
|
LANDER_DATA_DIR=data \
|
||||||
'$(BUILD_DIR)/$(BIN_FILENAME)'
|
'$(BUILD_DIR)/$(BIN_FILENAME)'
|
||||||
|
|
||||||
|
.PHONY: valgrind
|
||||||
|
valgrind: $(BIN)
|
||||||
|
LANDER_API_KEY=test \
|
||||||
|
LANDER_DATA_DIR=data \
|
||||||
|
valgrind '$(BUILD_DIR)/$(BIN_FILENAME)'
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: $(TARGETS_TEST)
|
test: $(TARGETS_TEST)
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,10 @@ typedef enum http_route_type {
|
||||||
* Function describing a step in a route's processing.
|
* Function describing a step in a route's processing.
|
||||||
*
|
*
|
||||||
* @param conn connection to process
|
* @param conn connection to process
|
||||||
* @return whether the processing can immediately advance to the next step. A
|
* @return whether processing can proceed to the next step without performing
|
||||||
* step should return false if it's e.g. waiting for I/O, and can therefore not
|
* I/O first. For a request step, `false` means more data needs to be read
|
||||||
* finish its task in the current cycle of the event loop.
|
* before the step can finish its processing. For response steps, `false` means
|
||||||
|
* there's new data in the write buffer that needs to be written.
|
||||||
*/
|
*/
|
||||||
typedef bool (*step)(event_loop_conn *conn);
|
typedef bool (*step)(event_loop_conn *conn);
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ typedef struct http_route {
|
||||||
// starting the http loop
|
// starting the http loop
|
||||||
regex_t *regex;
|
regex_t *regex;
|
||||||
step steps[HTTP_LOOP_MAX_STEPS];
|
step steps[HTTP_LOOP_MAX_STEPS];
|
||||||
|
step steps_res[HTTP_LOOP_MAX_STEPS];
|
||||||
} http_route;
|
} http_route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,14 +118,6 @@ typedef struct event_loop http_loop;
|
||||||
*/
|
*/
|
||||||
bool http_loop_handle_request(event_loop_conn *conn);
|
bool http_loop_handle_request(event_loop_conn *conn);
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the HTTP response to the file descriptor. This is the "write_data"
|
|
||||||
* function for the event loop.
|
|
||||||
*
|
|
||||||
* @param conn connection to process
|
|
||||||
*/
|
|
||||||
void http_loop_write_response(event_loop_conn *conn);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to parse the incoming data as an HTTP request.
|
* Try to parse the incoming data as an HTTP request.
|
||||||
*
|
*
|
||||||
|
@ -148,6 +142,14 @@ 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the response processing. This is the `write_data` function for the
|
||||||
|
* event loop.
|
||||||
|
*
|
||||||
|
* @param conn connection to process
|
||||||
|
*/
|
||||||
|
void http_loop_handle_response(event_loop_conn *conn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request step that consumes the request body and stores it in a buffer.
|
* Request step that consumes the request body and stores it in a buffer.
|
||||||
*
|
*
|
||||||
|
@ -180,6 +182,21 @@ bool http_loop_step_auth(event_loop_conn *conn);
|
||||||
*/
|
*/
|
||||||
bool http_loop_step_switch_res(event_loop_conn *conn);
|
bool http_loop_step_switch_res(event_loop_conn *conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the HTTP header back to the connection. If `res->head` is not set, a
|
||||||
|
* header will be generated for you.
|
||||||
|
*
|
||||||
|
* @param conn connection to process
|
||||||
|
*/
|
||||||
|
bool http_loop_step_write_header(event_loop_conn *conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the HTTP body back to the connection.
|
||||||
|
*
|
||||||
|
* @param conn connection to process
|
||||||
|
*/
|
||||||
|
bool http_loop_step_write_body(event_loop_conn *conn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new http loop.
|
* Initialize a new http loop.
|
||||||
*
|
*
|
||||||
|
|
|
@ -54,7 +54,7 @@ event_loop *http_loop_init(http_route *routes, size_t route_count,
|
||||||
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->write_data = http_loop_handle_response;
|
||||||
|
|
||||||
http_loop_gctx *gctx = http_loop_gctx_init();
|
http_loop_gctx *gctx = http_loop_gctx_init();
|
||||||
gctx->c = custom_gctx;
|
gctx->c = custom_gctx;
|
||||||
|
|
|
@ -138,13 +138,11 @@ void http_loop_process_request(event_loop_conn *conn) {
|
||||||
ctx->current_step++;
|
ctx->current_step++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conn->state != event_loop_conn_state_req) {
|
// Request processing can stop early by switching the connection state
|
||||||
return;
|
// Either way, we reset the step counter as it will be used by the response
|
||||||
}
|
// steps
|
||||||
|
if ((conn->state != event_loop_conn_state_req) ||
|
||||||
// If we've reached the end of the list of step functions, we report the
|
(ctx->route->steps[ctx->current_step] == NULL)) {
|
||||||
// request as finished by clearing its route
|
ctx->current_step = 0;
|
||||||
if (ctx->route->steps[ctx->current_step] == NULL) {
|
|
||||||
http_loop_ctx_reset(ctx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ void http_loop_init_header(http_response *res) {
|
||||||
res->head_len = buf_size + 1;
|
res->head_len = buf_size + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_loop_write_response(event_loop_conn *conn) {
|
bool http_loop_step_write_header(event_loop_conn *conn) {
|
||||||
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
|
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
|
||||||
|
|
||||||
// Create head response
|
// Create head response
|
||||||
|
@ -56,43 +56,68 @@ void http_loop_write_response(event_loop_conn *conn) {
|
||||||
http_loop_init_header(res);
|
http_loop_init_header(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final iteration marks the end of the response, after which we reset the
|
// Step has finished its work
|
||||||
// context so a next request can be processed
|
if (res->head_written == res->head_len) {
|
||||||
if (res->head_written == res->head_len &&
|
return true;
|
||||||
res->body.expected_len == 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,
|
||||||
size_t bytes_to_write = MIN(res->head_len - res->head_written,
|
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
|
||||||
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
|
memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written],
|
||||||
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;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http_loop_step_write_body(event_loop_conn *conn) {
|
||||||
|
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
|
||||||
|
|
||||||
|
if (res->body.expected_len == res->body.len) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_to_write = MIN(res->body.expected_len - res->body.len,
|
||||||
|
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
|
||||||
|
|
||||||
|
size_t bytes_written;
|
||||||
|
|
||||||
|
switch (res->body.type) {
|
||||||
|
case http_body_buf:
|
||||||
|
memcpy(&conn->wbuf[conn->wbuf_size], &(res->body.buf)[res->body.len],
|
||||||
bytes_to_write);
|
bytes_to_write);
|
||||||
|
|
||||||
conn->wbuf_size += bytes_to_write;
|
conn->wbuf_size += bytes_to_write;
|
||||||
res->head_written += bytes_to_write;
|
res->body.len += bytes_to_write;
|
||||||
|
break;
|
||||||
|
case http_body_file:
|
||||||
|
bytes_written = fread(&conn->wbuf[conn->wbuf_size], sizeof(uint8_t),
|
||||||
|
bytes_to_write, res->body.file);
|
||||||
|
conn->wbuf_size += bytes_written;
|
||||||
|
res->body.len += bytes_written;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res->body.len < res->body.expected_len) {
|
return false;
|
||||||
size_t bytes_to_write = MIN(res->body.expected_len - res->body.len,
|
}
|
||||||
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
|
|
||||||
size_t bytes_written;
|
|
||||||
|
|
||||||
switch (res->body.type) {
|
void http_loop_handle_response(event_loop_conn *conn) {
|
||||||
case http_body_buf:
|
http_loop_ctx *ctx = conn->ctx;
|
||||||
memcpy(&conn->wbuf[conn->wbuf_size], &(res->body.buf)[res->body.len],
|
|
||||||
bytes_to_write);
|
while ((conn->state == event_loop_conn_state_res) &&
|
||||||
conn->wbuf_size += bytes_to_write;
|
(ctx->route->steps_res[ctx->current_step] != NULL) &&
|
||||||
res->body.len += bytes_to_write;
|
ctx->route->steps_res[ctx->current_step](conn)) {
|
||||||
break;
|
ctx->current_step++;
|
||||||
case http_body_file:
|
}
|
||||||
bytes_written = fread(&conn->wbuf[conn->wbuf_size], sizeof(uint8_t),
|
|
||||||
bytes_to_write, res->body.file);
|
// Response processing can stop early be switching the connection state
|
||||||
conn->wbuf_size += bytes_written;
|
// After response processing has finished its work, we reset the context to
|
||||||
res->body.len += bytes_written;
|
// prepare for a new request
|
||||||
break;
|
if ((conn->state != event_loop_conn_state_res) ||
|
||||||
}
|
(ctx->route->steps_res[ctx->current_step] == NULL)) {
|
||||||
|
http_loop_ctx_reset(ctx);
|
||||||
|
|
||||||
|
conn->state = event_loop_conn_state_req;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,33 @@ http_route lander_routes[] = {
|
||||||
{.type = http_route_literal,
|
{.type = http_route_literal,
|
||||||
.method = http_get,
|
.method = http_get,
|
||||||
.path = "/",
|
.path = "/",
|
||||||
.steps = {lander_get_index, NULL}},
|
.steps = {lander_get_index, NULL},
|
||||||
{.type = http_route_regex,
|
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||||
.method = http_get,
|
NULL}},
|
||||||
.path = "^/([^/]+)$",
|
{
|
||||||
.steps = {lander_get_entry, NULL}},
|
.type = http_route_regex,
|
||||||
{.type = http_route_regex,
|
.method = http_get,
|
||||||
.method = http_post,
|
.path = "^/([^/]+)$",
|
||||||
.path = "^/s(l?)/([^/]*)$",
|
.steps = {lander_get_entry, NULL},
|
||||||
.steps = {http_loop_step_auth, http_loop_step_body_to_buf,
|
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||||
lander_post_redirect, NULL}},
|
NULL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.type = http_route_regex,
|
||||||
|
.method = http_post,
|
||||||
|
.path = "^/s(l?)/([^/]*)$",
|
||||||
|
.steps = {http_loop_step_auth, http_loop_step_body_to_buf,
|
||||||
|
lander_post_redirect, NULL},
|
||||||
|
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||||
|
NULL},
|
||||||
|
},
|
||||||
{.type = http_route_regex,
|
{.type = http_route_regex,
|
||||||
.method = http_post,
|
.method = http_post,
|
||||||
.path = "^/p(l?)/([^/]*)$",
|
.path = "^/p(l?)/([^/]*)$",
|
||||||
.steps = {http_loop_step_auth, lander_post_paste,
|
.steps = {http_loop_step_auth, lander_post_paste,
|
||||||
http_loop_step_body_to_file, http_loop_step_switch_res, NULL}},
|
http_loop_step_body_to_file, http_loop_step_switch_res, NULL},
|
||||||
|
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||||
|
NULL}},
|
||||||
};
|
};
|
||||||
|
|
||||||
void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); }
|
void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); }
|
||||||
|
|
Loading…
Reference in New Issue