chore(lander): removed old networking code
parent
1a7686003c
commit
c59dd29648
4
Makefile
4
Makefile
|
@ -111,12 +111,14 @@ $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
|
|||
lint:
|
||||
clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL)
|
||||
make -C lsm lint
|
||||
make -C lnm lint
|
||||
make -C landerctl lint
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL)
|
||||
make -C lsm fmt
|
||||
make -C lnm fmt
|
||||
make -C landerctl fmt
|
||||
|
||||
.PHONY: check
|
||||
|
@ -133,12 +135,14 @@ check:
|
|||
-j$(shell nproc) \
|
||||
$(SRCS)
|
||||
make -C lsm check
|
||||
make -C lnm check
|
||||
make -C landerctl check
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
$(MAKE) -C lsm clean
|
||||
$(MAKE) -C lnm clean
|
||||
$(MAKE) -C landerctl clean
|
||||
|
||||
.PHONY: bear
|
||||
|
|
|
@ -8,7 +8,7 @@ TEST_DIR = test
|
|||
THIRDPARTY_DIR = thirdparty
|
||||
|
||||
INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include
|
||||
LIBS = m lsm lnm
|
||||
LIBS = lsm lnm
|
||||
LIB_DIRS = ./lsm/build ./lnm/build
|
||||
|
||||
# -MMD: generate a .d file for every source file. This file can be imported by
|
||||
|
|
|
@ -54,7 +54,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) {
|
|||
|
||||
for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) {
|
||||
route = gctx->routes.arr[i];
|
||||
bool matched_path;
|
||||
bool matched_path = false;
|
||||
|
||||
switch (route->type) {
|
||||
case lnm_http_route_type_literal:
|
||||
|
@ -238,7 +238,7 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) {
|
|||
|
||||
size_t to_write =
|
||||
LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size);
|
||||
size_t written;
|
||||
size_t written = 0;
|
||||
|
||||
switch (res->body.type) {
|
||||
case lnm_http_res_body_type_buf:
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "event_loop.h"
|
||||
#include "log.h"
|
||||
|
||||
static void event_loop_fd_set_nb(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
fcntl(fd, F_SETFL, flags);
|
||||
}
|
||||
|
||||
event_loop *event_loop_init() {
|
||||
event_loop *el = calloc(sizeof(event_loop), 1);
|
||||
|
||||
// No idea if this is a good starter value
|
||||
el->connections = calloc(sizeof(event_loop_conn *), 16);
|
||||
el->connection_count = 16;
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
int event_loop_put(event_loop *el, event_loop_conn *conn) {
|
||||
if ((size_t)conn->fd >= el->connection_count) {
|
||||
event_loop_conn **resized =
|
||||
realloc(el->connections, sizeof(event_loop_conn *) * (conn->fd + 1));
|
||||
|
||||
if (resized == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
el->connections = resized;
|
||||
el->connection_count = conn->fd + 1;
|
||||
}
|
||||
|
||||
el->connections[conn->fd] = conn;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int event_loop_accept(event_loop *el, int fd) {
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t socklen = sizeof(client_addr);
|
||||
int connfd = accept(fd, (struct sockaddr *)&client_addr, &socklen);
|
||||
|
||||
if (connfd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// set the new connection fd to nonblocking mode
|
||||
event_loop_fd_set_nb(connfd);
|
||||
|
||||
// creating the struct Conn
|
||||
event_loop_conn *conn = event_loop_conn_init(el);
|
||||
|
||||
// Close the connectoin if we fail to allocate a connection struct
|
||||
if (conn == NULL) {
|
||||
close(connfd);
|
||||
|
||||
return -3;
|
||||
}
|
||||
|
||||
conn->fd = connfd;
|
||||
conn->state = event_loop_conn_state_req;
|
||||
|
||||
int res = event_loop_put(el, conn);
|
||||
|
||||
if (res != 0) {
|
||||
close(connfd);
|
||||
|
||||
return -4;
|
||||
}
|
||||
|
||||
debug("Connection established on fd %i", connfd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void event_loop_run(event_loop *el, int port) {
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (fd < 0) {
|
||||
critical(1, "Failed to open listening socket, errno: %i", errno);
|
||||
}
|
||||
|
||||
int val = 1;
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
||||
|
||||
// bind
|
||||
struct sockaddr_in addr = {.sin_family = AF_INET,
|
||||
.sin_port = ntohs(port),
|
||||
.sin_addr.s_addr = ntohl(0)};
|
||||
|
||||
int res = bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
|
||||
|
||||
if (res) {
|
||||
critical(1, "Failed to bind listening socket, errno: %i", errno);
|
||||
}
|
||||
|
||||
debug("Listening socket bound to fd %i", fd);
|
||||
|
||||
res = listen(fd, SOMAXCONN);
|
||||
|
||||
if (res) {
|
||||
critical(1, "Failed to start listening on listening socket, errno: %i",
|
||||
errno);
|
||||
}
|
||||
|
||||
// The listening socket is always poll'ed in non-blocking mode as well
|
||||
event_loop_fd_set_nb(fd);
|
||||
|
||||
// TODO don't hardcode the number 32
|
||||
struct pollfd *poll_args = calloc(sizeof(struct pollfd), 32);
|
||||
|
||||
// for convenience, the listening fd is put in the first position
|
||||
struct pollfd pfd = {fd, POLLIN, 0};
|
||||
poll_args[0] = pfd;
|
||||
|
||||
event_loop_conn *conn;
|
||||
int events;
|
||||
|
||||
info("Starting event loop on port %i", port);
|
||||
|
||||
while (1) {
|
||||
size_t poll_args_count = 1;
|
||||
|
||||
// connection fds
|
||||
for (size_t i = 0; i < el->connection_count; i++) {
|
||||
conn = el->connections[i];
|
||||
|
||||
if (conn == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
events = (conn->state == event_loop_conn_state_req) ? POLLIN : POLLOUT;
|
||||
events |= POLLERR;
|
||||
|
||||
pfd.fd = conn->fd;
|
||||
pfd.events = events;
|
||||
|
||||
poll_args[poll_args_count] = pfd;
|
||||
poll_args_count++;
|
||||
|
||||
// We do at most 32 connections at a time for now
|
||||
if (poll_args_count == 32)
|
||||
break;
|
||||
}
|
||||
|
||||
// poll for active fds
|
||||
// the timeout argument doesn't matter here
|
||||
int rv = poll(poll_args, (nfds_t)poll_args_count, -1);
|
||||
|
||||
if (rv < 0) {
|
||||
critical(1, "Poll failed, errno: %i", errno);
|
||||
}
|
||||
|
||||
// process active connections
|
||||
for (size_t i = 1; i < poll_args_count; i++) {
|
||||
if (poll_args[i].revents) {
|
||||
conn = el->connections[poll_args[i].fd];
|
||||
|
||||
event_loop_conn_io(el, conn);
|
||||
|
||||
if (conn->state == event_loop_conn_state_end) {
|
||||
// client closed normally, or something bad happened.
|
||||
// destroy this connection
|
||||
el->connections[conn->fd] = NULL;
|
||||
|
||||
close(conn->fd);
|
||||
debug("Connection closed on fd %i", conn->fd);
|
||||
event_loop_conn_free(el, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to accept a new connection if the listening fd is active
|
||||
if (poll_args[0].revents) {
|
||||
event_loop_accept(el, fd);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#include "event_loop.h"
|
||||
|
||||
event_loop_conn *event_loop_conn_init(event_loop *el) {
|
||||
event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1);
|
||||
conn->ctx = el->ctx_init(el->gctx);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
void event_loop_conn_free(event_loop *el, event_loop_conn *conn) {
|
||||
el->ctx_free(conn->ctx);
|
||||
free(conn);
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "event_loop.h"
|
||||
|
||||
void event_loop_conn_io_res(event_loop *el, event_loop_conn *conn) {
|
||||
do {
|
||||
ssize_t res = 0;
|
||||
size_t remain = conn->wbuf_size - conn->wbuf_sent;
|
||||
|
||||
do {
|
||||
res = write(conn->fd, &conn->wbuf[conn->wbuf_sent], remain);
|
||||
} while (res < 0 && errno == EINTR);
|
||||
|
||||
// EAGAIN doesn't mean there was an error, but rather that there's no more
|
||||
// data right now, but there might be more later, aka "try again later"
|
||||
if (res < 0 && errno == EAGAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not EGAIN, there was an error writing so we simply end the
|
||||
// request
|
||||
if (res < 0) {
|
||||
conn->state = event_loop_conn_state_end;
|
||||
return;
|
||||
}
|
||||
|
||||
conn->wbuf_sent += (size_t)res;
|
||||
|
||||
// After writing the entire buffer, we run the write_data command to receive
|
||||
// new data, or exit the loop
|
||||
if (conn->wbuf_sent == conn->wbuf_size) {
|
||||
conn->wbuf_sent = 0;
|
||||
conn->wbuf_size = 0;
|
||||
|
||||
el->write_data(conn);
|
||||
}
|
||||
} while (conn->state == event_loop_conn_state_res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read new data into the read buffer. This command performs at most one
|
||||
* successful read syscall.
|
||||
*
|
||||
* Returns whether the function should be retried immediately or not.
|
||||
*/
|
||||
void event_loop_conn_io_req(event_loop *el, event_loop_conn *conn) {
|
||||
do {
|
||||
// Move remaining data to start of buffer
|
||||
memmove(conn->rbuf, &conn->rbuf[conn->rbuf_read],
|
||||
conn->rbuf_size - conn->rbuf_read);
|
||||
conn->rbuf_size -= conn->rbuf_read;
|
||||
conn->rbuf_read = 0;
|
||||
|
||||
ssize_t res;
|
||||
size_t cap = EVENT_LOOP_BUFFER_SIZE - conn->rbuf_size;
|
||||
|
||||
// Try to read at most cap bytes from the file descriptor
|
||||
do {
|
||||
res = read(conn->fd, &conn->rbuf[conn->rbuf_size], cap);
|
||||
} while (res < 0 && errno == EINTR);
|
||||
|
||||
// EGAIN means we try again later
|
||||
if (res < 0 && errno == EAGAIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Any other negative error message means the read errored out. If res is 0,
|
||||
// we've reached the end of the input which (usually) means the remote peer
|
||||
// has closed the connection. Either way, we close the connection.
|
||||
if (res <= 0) {
|
||||
conn->state = event_loop_conn_state_end;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conn->rbuf_size += (size_t)res;
|
||||
|
||||
// This loop allows processing multiple requests from a single read buffer
|
||||
while (el->handle_data(conn))
|
||||
;
|
||||
}
|
||||
// We can keep reading as long as we're in request mode
|
||||
while (conn->state == event_loop_conn_state_req);
|
||||
}
|
||||
|
||||
void event_loop_conn_io(event_loop *el, event_loop_conn *conn) {
|
||||
switch (conn->state) {
|
||||
case event_loop_conn_state_req:
|
||||
event_loop_conn_io_req(el, conn);
|
||||
break;
|
||||
case event_loop_conn_state_res:
|
||||
event_loop_conn_io_res(el, conn);
|
||||
break;
|
||||
case event_loop_conn_state_end:;
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "http/types.h"
|
||||
|
||||
// Very important that this is in the same order as http_request_method
|
||||
const char *http_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"};
|
||||
const size_t http_method_names_len =
|
||||
sizeof(http_method_names) / sizeof(http_method_names[0]);
|
||||
|
||||
// clang-format off
|
||||
|
||||
const char *http_status_names[][32] = {
|
||||
// 1xx
|
||||
{
|
||||
"Continue", // 100
|
||||
"Switching Protocols", // 101,
|
||||
"Processing", // 102
|
||||
"Early Hints", // 103
|
||||
},
|
||||
// 2xx
|
||||
{
|
||||
"OK", // 200
|
||||
"Created", // 201
|
||||
"Accepted", // 202
|
||||
"Non-Authoritative Information", // 203
|
||||
"No Content", // 204
|
||||
"Reset Content", // 205
|
||||
"Partial Content", // 206
|
||||
"Multi-Status", // 207
|
||||
"Already Reported", // 208
|
||||
},
|
||||
// 3xx
|
||||
{
|
||||
"Multiple Choices", // 300
|
||||
"Moved Permanently", // 301
|
||||
"Found", // 302
|
||||
"See Other", // 303
|
||||
"Not Modified", // 304
|
||||
NULL, // 305
|
||||
NULL, // 306
|
||||
"Temporary Redirect", // 307
|
||||
"Permanent Redirect", // 308
|
||||
},
|
||||
// 4xx
|
||||
{
|
||||
"Bad Request", // 400
|
||||
"Unauthorized", // 401
|
||||
"Payment Required", // 402
|
||||
"Forbidden", // 403
|
||||
"Not Found", // 404
|
||||
"Method Not Allowed", // 405
|
||||
"Not Acceptable", // 406
|
||||
"Proxy Authentication Required", // 407
|
||||
"Request Timeout", // 408
|
||||
"Conflict", // 409
|
||||
"Gone", // 410
|
||||
"Length Required", // 411
|
||||
"Precondition Failed", // 412
|
||||
"Content Too Large", // 413
|
||||
"URI Too Long", // 414
|
||||
"Unsupported Media Type", // 415
|
||||
"Range Not Satisfiable", // 416
|
||||
"Expectation Failed", // 417
|
||||
"I'm a teapot", // 418
|
||||
NULL, // 419
|
||||
NULL, // 420
|
||||
"Misdirected Request", // 421
|
||||
"Unprocessable Content", // 422
|
||||
"Locked", // 423
|
||||
"Failed Dependency", // 424
|
||||
"Too Early", // 425
|
||||
"Upgrade Required", // 426
|
||||
NULL, // 427
|
||||
"Precondition Required", // 428
|
||||
"Too Many Requests", // 429
|
||||
NULL, // 430
|
||||
"Request Header Fields Too Large", // 431
|
||||
},
|
||||
// 5xx
|
||||
{
|
||||
"Internal Server Error", // 500
|
||||
"Not Implemented", // 501
|
||||
"Bad Gateway", // 502
|
||||
"Service Unavailable", // 503
|
||||
"Gateway Timeout", // 504
|
||||
"HTTP Version Not Supported", // 505
|
||||
"Variant Also Negotiates", // 506
|
||||
"Insufficient Storage", // 507
|
||||
"Loop Detected", // 508
|
||||
NULL, // 509
|
||||
"Not Extended", // 510
|
||||
"Network Authentication Required" // 511
|
||||
},
|
||||
};
|
||||
|
||||
const char *http_header_names[] = {
|
||||
"Connection",
|
||||
"Location",
|
||||
"Content-Type",
|
||||
"Content-Disposition",
|
||||
"Server",
|
||||
"Content-Length"
|
||||
};
|
||||
|
||||
const char *http_mime_type_names[][2] = {
|
||||
{ "aac", "audio/aac" },
|
||||
{ "bz", "application/x-bzip" },
|
||||
{ "bz2", "application/x-bzip2" },
|
||||
{ "css", "text/css" },
|
||||
{ "csv", "text/csv" },
|
||||
{ "gz", "application/gzip" },
|
||||
{ "gif", "image/gif" },
|
||||
{ "htm", "text/html" },
|
||||
{ "html", "text/html" },
|
||||
{ "jar", "application/java-archive" },
|
||||
{ "jpeg", "image/jpeg" },
|
||||
{ "jpg", "image/jpeg" },
|
||||
{ "js", "text/javascript" },
|
||||
{ "json", "application/json" },
|
||||
{ "mp3", "audio/mpeg" },
|
||||
{ "mp4", "video/mp4" },
|
||||
{ "png", "image/png" },
|
||||
{ "pdf", "application/pdf" },
|
||||
{ "rar", "application/vnd.rar" },
|
||||
{ "sh", "application/x-sh" },
|
||||
{ "svg", "image/svg+xml" },
|
||||
{ "tar", "application/x-tar" },
|
||||
{ "txt", "text/plain" },
|
||||
{ "wav", "audio/wav" },
|
||||
{ "7z", "application/x-7z-compressed" },
|
||||
};
|
||||
|
||||
// clang-format on
|
|
@ -1,52 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "http/res.h"
|
||||
|
||||
void http_res_set_body_buf(http_response *res, const char *body,
|
||||
size_t body_len, bool owned) {
|
||||
res->body.type = http_body_buf;
|
||||
res->body.buf = (char *)body;
|
||||
res->body.expected_len = body_len;
|
||||
res->body.buf_owned = owned;
|
||||
}
|
||||
|
||||
void http_res_set_body_file(http_response *res, const char *filename) {
|
||||
struct stat st;
|
||||
stat(filename, &st);
|
||||
|
||||
// TODO error handling
|
||||
FILE *f = fopen(filename, "r");
|
||||
|
||||
res->body.type = http_body_file;
|
||||
res->body.file = f;
|
||||
res->body.expected_len = st.st_size;
|
||||
}
|
||||
|
||||
void http_res_add_header(http_response *res, http_header type,
|
||||
const char *value, bool owned) {
|
||||
// Extend the header array
|
||||
if (res->headers.len == res->headers.cap) {
|
||||
http_response_header *new_arr = realloc(
|
||||
res->headers.arr, 2 * res->headers.cap * sizeof(http_response_header));
|
||||
|
||||
if (new_arr == NULL) {
|
||||
// TODO error handling
|
||||
return;
|
||||
}
|
||||
|
||||
res->headers.arr = new_arr;
|
||||
res->headers.cap *= 2;
|
||||
}
|
||||
|
||||
res->headers.arr[res->headers.len].type = type;
|
||||
res->headers.arr[res->headers.len].value = value;
|
||||
res->headers.arr[res->headers.len].owned = owned;
|
||||
|
||||
res->headers.len++;
|
||||
}
|
||||
|
||||
void http_res_set_mime_type(http_response *res, http_mime_type mime_type) {
|
||||
http_res_add_header(res, http_header_content_type,
|
||||
http_mime_type_names[mime_type][1], false);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "http/types.h"
|
||||
|
||||
http_body *http_body_init() { return calloc(sizeof(http_body), 1); }
|
||||
|
||||
void http_body_reset(http_body *body) {
|
||||
if (body->type == http_body_file) {
|
||||
fclose(body->file);
|
||||
}
|
||||
|
||||
if (body->buf_owned) {
|
||||
free(body->buf);
|
||||
}
|
||||
|
||||
if (body->fname_owned) {
|
||||
free(body->fname);
|
||||
}
|
||||
|
||||
body->type = 0;
|
||||
body->buf = NULL;
|
||||
body->buf_owned = false;
|
||||
body->file = NULL;
|
||||
body->fname = NULL;
|
||||
body->fname_owned = false;
|
||||
body->expected_len = 0;
|
||||
body->len = 0;
|
||||
}
|
||||
|
||||
void http_body_free(http_body *body) {
|
||||
http_body_reset(body);
|
||||
free(body);
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
#include <regex.h>
|
||||
|
||||
#include "http/types.h"
|
||||
#include "http_loop.h"
|
||||
#include "log.h"
|
||||
|
||||
const http_step http_default_res_steps[HTTP_LOOP_MAX_STEPS] = {
|
||||
http_loop_step_write_header, http_loop_step_write_body, NULL};
|
||||
|
||||
bool http_loop_handle_request(event_loop_conn *conn) {
|
||||
// Prevents the request handler function from looping indefinitely without
|
||||
// ever consuming new data
|
||||
if (conn->rbuf_size - conn->rbuf_read == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
// If route is defined, we're currently processing a request
|
||||
if (ctx->route == NULL) {
|
||||
http_parse_error res = http_loop_parse_request(conn);
|
||||
|
||||
if (res == http_parse_error_invalid ||
|
||||
(res == http_parse_error_incomplete &&
|
||||
conn->rbuf_size == EVENT_LOOP_BUFFER_SIZE)) {
|
||||
conn->state = event_loop_conn_state_end;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
conn->rbuf_read += ctx->req.len;
|
||||
|
||||
// It's fun to respond with extremely specific error messages
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501
|
||||
if (res == http_parse_error_unknown_method) {
|
||||
ctx->res.status = 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);
|
||||
}
|
||||
|
||||
// TODO in highly concurrent situations, it might actually be better to always
|
||||
// return false here, as this allows cycling better through all connections
|
||||
return conn->state == event_loop_conn_state_req;
|
||||
}
|
||||
|
||||
event_loop *http_loop_init(http_route *routes, size_t route_count,
|
||||
void *custom_gctx, void *(*custom_ctx_init)(),
|
||||
void(custom_ctx_reset)(), void(custom_ctx_free)()) {
|
||||
event_loop *el = event_loop_init();
|
||||
|
||||
el->ctx_init = (void *(*)(void *))http_loop_ctx_init;
|
||||
el->ctx_free = (void (*)(void *))http_loop_ctx_free;
|
||||
el->handle_data = http_loop_handle_request;
|
||||
el->write_data = http_loop_handle_response;
|
||||
|
||||
http_loop_gctx *gctx = http_loop_gctx_init();
|
||||
gctx->c = custom_gctx;
|
||||
gctx->routes = routes;
|
||||
gctx->route_count = route_count;
|
||||
gctx->custom_ctx_init = custom_ctx_init;
|
||||
gctx->custom_ctx_reset = custom_ctx_reset;
|
||||
gctx->custom_ctx_free = custom_ctx_free;
|
||||
el->gctx = gctx;
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
void http_loop_set_api_key(http_loop *hl, const char *api_key) {
|
||||
http_loop_gctx *gctx = hl->gctx;
|
||||
gctx->api_key = api_key;
|
||||
}
|
||||
|
||||
void http_loop_run(event_loop *el, int port) {
|
||||
debug("Compiling RegEx routes");
|
||||
|
||||
http_loop_gctx *gctx = el->gctx;
|
||||
|
||||
for (size_t i = 0; i < gctx->route_count; i++) {
|
||||
http_route *route = &gctx->routes[i];
|
||||
|
||||
if (route->type == http_route_regex) {
|
||||
regex_t *r = calloc(sizeof(regex_t), 1);
|
||||
|
||||
if (regcomp(r, route->path, REG_EXTENDED) != 0) {
|
||||
critical(1, "RegEx expression '%s' failed to compile", route->path);
|
||||
}
|
||||
|
||||
route->regex = r;
|
||||
}
|
||||
}
|
||||
|
||||
debug("RegEx routes compiled successfully");
|
||||
|
||||
event_loop_run(el, port);
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include "http/types.h"
|
||||
#include "http_loop.h"
|
||||
|
||||
http_loop_gctx *http_loop_gctx_init() {
|
||||
http_loop_gctx *gctx = calloc(sizeof(http_loop_gctx), 1);
|
||||
|
||||
return gctx;
|
||||
}
|
||||
|
||||
http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) {
|
||||
http_loop_ctx *ctx = calloc(sizeof(http_loop_ctx), 1);
|
||||
ctx->g = g;
|
||||
ctx->c = g->custom_ctx_init();
|
||||
|
||||
// TODO error checking
|
||||
ctx->res.headers.arr = malloc(4 * sizeof(http_response_header));
|
||||
ctx->res.headers.cap = 4;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void http_loop_ctx_free(http_loop_ctx *ctx) {
|
||||
http_loop_ctx_reset(ctx);
|
||||
ctx->g->custom_ctx_free(ctx->c);
|
||||
|
||||
if (ctx->res.headers.cap > 0) {
|
||||
free(ctx->res.headers.arr);
|
||||
}
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void http_loop_ctx_reset(http_loop_ctx *ctx) {
|
||||
ctx->route = NULL;
|
||||
ctx->current_step = 0;
|
||||
|
||||
if (ctx->res.head != NULL) {
|
||||
free((void *)ctx->res.head);
|
||||
ctx->res.head = NULL;
|
||||
}
|
||||
|
||||
http_body_reset(&ctx->req.body);
|
||||
http_body_reset(&ctx->res.body);
|
||||
|
||||
for (size_t i = 0; i < ctx->res.headers.len; i++) {
|
||||
if (ctx->res.headers.arr[i].owned) {
|
||||
free((void *)ctx->res.headers.arr[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't set the cap as we can just keep the original array for the next
|
||||
// requests
|
||||
ctx->res.headers.len = 0;
|
||||
|
||||
ctx->res.status = 0;
|
||||
ctx->res.head_len = 0;
|
||||
ctx->res.head_written = 0;
|
||||
|
||||
ctx->g->custom_ctx_reset(ctx->c);
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "http_loop.h"
|
||||
#include "log.h"
|
||||
|
||||
http_parse_error http_loop_parse_request(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
// First we try to parse the incoming HTTP request
|
||||
size_t num_headers = HTTP_MAX_ALLOWED_HEADERS;
|
||||
http_request *req = &ctx->req;
|
||||
|
||||
const char *method;
|
||||
size_t method_len;
|
||||
char *path;
|
||||
size_t path_len;
|
||||
|
||||
int res =
|
||||
phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read],
|
||||
conn->rbuf_size - conn->rbuf_read, &method, &method_len,
|
||||
(const char **)&path, &path_len, &req->minor_version,
|
||||
req->headers, &num_headers, 0);
|
||||
|
||||
if (res == -1) {
|
||||
return http_parse_error_invalid;
|
||||
} else if (res == -2) {
|
||||
return http_parse_error_incomplete;
|
||||
}
|
||||
|
||||
req->num_headers = num_headers;
|
||||
req->len = res;
|
||||
|
||||
// Try to parse the method type
|
||||
bool match = false;
|
||||
size_t i = 0;
|
||||
|
||||
for (i = 0; i < http_method_names_len; i++) {
|
||||
if (strncmp(method, http_method_names[i], method_len) == 0) {
|
||||
req->method = i;
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return http_parse_error_unknown_method;
|
||||
}
|
||||
|
||||
// Split path into path & query
|
||||
i = 0;
|
||||
bool no_query = true;
|
||||
|
||||
while (no_query && i < path_len) {
|
||||
if (path[i] == '?') {
|
||||
// Ensure we don't store an invalid pointer if the request simply ends
|
||||
// with '?'
|
||||
if (i + 1 < req->path_len) {
|
||||
req->query = &path[i + 1];
|
||||
req->query_len = path_len - (i + 1);
|
||||
}
|
||||
|
||||
path_len = i;
|
||||
|
||||
no_query = false;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// The path needs to be NULL-terminated in order for regex routes to be
|
||||
// matched properly. We know we can overwrite this char because it's either
|
||||
// '?' if there's a query, or '\n' because we know the buf contains a valid
|
||||
// HTTP rqeuest
|
||||
path[path_len] = '\0';
|
||||
|
||||
req->path = path;
|
||||
req->path_len = path_len;
|
||||
|
||||
// Ensure we clear the old request's query
|
||||
if (no_query) {
|
||||
req->query = NULL;
|
||||
req->query_len = 0;
|
||||
}
|
||||
|
||||
return http_parse_error_ok;
|
||||
}
|
||||
|
||||
void http_loop_route_request(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
http_loop_gctx *gctx = ctx->g;
|
||||
|
||||
info("%s %.*s", http_method_names[ctx->req.method], ctx->req.path_len,
|
||||
ctx->req.path);
|
||||
|
||||
http_route *route;
|
||||
bool path_matched = false;
|
||||
|
||||
for (size_t i = 0; i < gctx->route_count; i++) {
|
||||
route = &gctx->routes[i];
|
||||
|
||||
switch (route->type) {
|
||||
case http_route_literal:
|
||||
if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) {
|
||||
path_matched = true;
|
||||
|
||||
if (ctx->req.method == route->method) {
|
||||
ctx->route = route;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case http_route_regex:
|
||||
if (regexec(route->regex, ctx->req.path, HTTP_MAX_REGEX_GROUPS,
|
||||
ctx->req.regex_groups, 0) == 0) {
|
||||
path_matched = true;
|
||||
|
||||
if (ctx->req.method == route->method) {
|
||||
ctx->route = route;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallthrough case
|
||||
ctx->res.status = path_matched ? http_method_not_allowed : http_not_found;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
// Request processing can stop early by switching the connection state
|
||||
// Either way, we reset the step counter as it will be used by the response
|
||||
// steps
|
||||
if ((conn->state != event_loop_conn_state_req) ||
|
||||
(ctx->route->steps[ctx->current_step] == NULL)) {
|
||||
ctx->current_step = 0;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
#include "http_loop.h"
|
||||
#include "log.h"
|
||||
|
||||
static const char *server = "lander/" LANDER_VERSION;
|
||||
|
||||
static int num_digits(size_t n) {
|
||||
int digits = 1;
|
||||
|
||||
while (n > 9) {
|
||||
digits++;
|
||||
|
||||
n /= 10;
|
||||
}
|
||||
|
||||
return digits;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function precalculates the size of the total buffer required using
|
||||
* snprintf. When this function is called with a buf size of 0, it never tries
|
||||
* to write any data, but it does return the amount of bytes that would be
|
||||
* written.
|
||||
*/
|
||||
void http_loop_init_header(http_response *res) {
|
||||
if (res->status == 0) {
|
||||
res->status = http_ok;
|
||||
}
|
||||
|
||||
http_res_add_header(res, http_header_server, server, false);
|
||||
|
||||
char *content_length_header = malloc(num_digits(res->body.expected_len) + 1);
|
||||
sprintf(content_length_header, "%zu", res->body.expected_len);
|
||||
|
||||
http_res_add_header(res, http_header_content_length, content_length_header,
|
||||
true);
|
||||
|
||||
const char *response_type_name =
|
||||
http_status_names[res->status / 100 - 1][res->status % 100];
|
||||
|
||||
// First we calculate the size of the start of the header
|
||||
int buf_size =
|
||||
snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name);
|
||||
|
||||
// We add each header's required size
|
||||
for (size_t i = 0; i < res->headers.len; i++) {
|
||||
buf_size += snprintf(NULL, 0, "%s: %s\n",
|
||||
http_header_names[res->headers.arr[i].type],
|
||||
res->headers.arr[i].value);
|
||||
}
|
||||
|
||||
// The + 1 is required to store the final null byte, but we will replace it
|
||||
// with the required final newline
|
||||
char *buf = malloc(buf_size + 1);
|
||||
buf_size = sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name);
|
||||
|
||||
for (size_t i = 0; i < res->headers.len; i++) {
|
||||
buf_size += sprintf(&buf[buf_size], "%s: %s\n",
|
||||
http_header_names[res->headers.arr[i].type],
|
||||
res->headers.arr[i].value);
|
||||
}
|
||||
|
||||
buf[buf_size] = '\n';
|
||||
|
||||
res->head = buf;
|
||||
res->head_len = buf_size + 1;
|
||||
}
|
||||
|
||||
bool http_loop_step_write_header(event_loop_conn *conn) {
|
||||
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
|
||||
|
||||
// Create head response
|
||||
if (res->head == NULL) {
|
||||
http_loop_init_header(res);
|
||||
}
|
||||
|
||||
// Step has finished its work
|
||||
if (res->head_written == res->head_len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
conn->wbuf_size += 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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void http_loop_handle_response(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
// Non-routed requests also need to be processed
|
||||
const http_step *steps =
|
||||
ctx->route != NULL ? ctx->route->steps_res : http_default_res_steps;
|
||||
|
||||
while ((conn->state == event_loop_conn_state_res) &&
|
||||
(steps[ctx->current_step] != NULL) && steps[ctx->current_step](conn)) {
|
||||
ctx->current_step++;
|
||||
}
|
||||
|
||||
// Response processing can stop early be switching the connection state
|
||||
// After response processing has finished its work, we reset the context to
|
||||
// prepare for a new request
|
||||
if ((conn->state != event_loop_conn_state_res) ||
|
||||
(steps[ctx->current_step] == NULL)) {
|
||||
http_loop_ctx_reset(ctx);
|
||||
|
||||
conn->state = event_loop_conn_state_req;
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "http_loop.h"
|
||||
#include "lander.h"
|
||||
|
||||
// Just a naive pow implementation; might improve later
|
||||
static uint64_t ipow(uint64_t base, uint64_t power) {
|
||||
uint64_t res = 1;
|
||||
|
||||
while (power > 0) {
|
||||
res *= base;
|
||||
power--;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts a string to a number, returning true if the string contained a valid
|
||||
* positive number.
|
||||
*/
|
||||
static bool string_to_num(size_t *res, const char *s, size_t len) {
|
||||
*res = 0;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
int val = s[i] - '0';
|
||||
|
||||
if (val < 0 || val > 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*res += (uint64_t)val * ipow(10, (len - 1) - i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool http_loop_step_parse_content_length(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
for (size_t i = 0; i < ctx->req.num_headers; i++) {
|
||||
const struct phr_header *header = &ctx->req.headers[i];
|
||||
|
||||
if (strncmp(header->name, "Content-Length", header->name_len) == 0) {
|
||||
// If the content length header is present but contains an invalid
|
||||
// number, we return a bad request error
|
||||
if (!string_to_num(&ctx->req.body.expected_len, header->value,
|
||||
header->value_len)) {
|
||||
ctx->res.status = http_bad_request;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
|
||||
return true;
|
||||
}
|
||||
// The content length was actually 0, so we can instantly return here
|
||||
else if (ctx->req.body.expected_len == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A zero here means there's no content length header
|
||||
if (ctx->req.body.expected_len == 0) {
|
||||
ctx->res.status = http_length_required;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to find and parse the Content-Length header. This function returns true
|
||||
* if it was successful. If false is returned, the underlying step should
|
||||
* immediately exit.
|
||||
*/
|
||||
bool try_parse_content_length(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
for (size_t i = 0; i < ctx->req.num_headers; i++) {
|
||||
const struct phr_header *header = &ctx->req.headers[i];
|
||||
|
||||
if (strncmp(header->name, "Content-Length", header->name_len) == 0) {
|
||||
// If the content length header is present but contains an invalid
|
||||
// number, we return a bad request error
|
||||
if (!string_to_num(&ctx->req.body.expected_len, header->value,
|
||||
header->value_len)) {
|
||||
ctx->res.status = http_bad_request;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
|
||||
return false;
|
||||
}
|
||||
// The content length was actually 0, so we can instantly return here
|
||||
else if (ctx->req.body.expected_len == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A zero here means there's no content length header
|
||||
if (ctx->req.body.expected_len == 0) {
|
||||
ctx->res.status = http_length_required;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool http_loop_step_body_to_buf(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
if (ctx->req.body.expected_len == 0) {
|
||||
if (!try_parse_content_length(conn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx->req.body.type = http_body_buf;
|
||||
ctx->req.body.buf = malloc(ctx->req.body.expected_len * sizeof(char));
|
||||
ctx->req.body.len = 0;
|
||||
}
|
||||
|
||||
size_t bytes_to_copy = MIN(conn->rbuf_size - conn->rbuf_read,
|
||||
ctx->req.body.expected_len - ctx->req.body.len);
|
||||
memcpy(&ctx->req.body.buf[ctx->req.body.len], &conn->rbuf[conn->rbuf_read],
|
||||
bytes_to_copy);
|
||||
ctx->req.body.len += bytes_to_copy;
|
||||
conn->rbuf_read += bytes_to_copy;
|
||||
|
||||
return ctx->req.body.len == ctx->req.body.expected_len;
|
||||
}
|
||||
|
||||
bool http_loop_step_body_to_file(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
if (ctx->req.body.expected_len == 0) {
|
||||
if (!try_parse_content_length(conn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx->req.body.type = http_body_file;
|
||||
ctx->req.body.file = fopen(ctx->req.body.fname, "wb");
|
||||
ctx->req.body.len = 0;
|
||||
}
|
||||
|
||||
size_t bytes_to_write = MIN(conn->rbuf_size - conn->rbuf_read,
|
||||
ctx->req.body.expected_len - ctx->req.body.len);
|
||||
size_t bytes_written = fwrite(&conn->rbuf[conn->rbuf_read], sizeof(uint8_t),
|
||||
bytes_to_write, ctx->req.body.file);
|
||||
ctx->req.body.len += bytes_written;
|
||||
conn->rbuf_read += bytes_written;
|
||||
|
||||
return ctx->req.body.len == ctx->req.body.expected_len;
|
||||
}
|
||||
|
||||
bool http_loop_step_auth(event_loop_conn *conn) {
|
||||
http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
for (size_t i = 0; i < ctx->req.num_headers; i++) {
|
||||
const struct phr_header *header = &ctx->req.headers[i];
|
||||
|
||||
if ((strncmp("X-Api-Key", header->name, header->name_len) == 0) &&
|
||||
(strncmp(header->value, ctx->g->api_key, header->value_len) == 0) &&
|
||||
(strlen(ctx->g->api_key) == header->value_len)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->res.status = http_unauthorized;
|
||||
conn->state = event_loop_conn_state_res;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool http_loop_step_switch_res(event_loop_conn *conn) {
|
||||
conn->state = event_loop_conn_state_res;
|
||||
|
||||
return true;
|
||||
}
|
|
@ -11,55 +11,6 @@
|
|||
const char lander_key_charset[] =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
http_route lander_routes[] = {
|
||||
{.type = http_route_literal,
|
||||
.method = http_get,
|
||||
.path = "/",
|
||||
.steps = {lander_get_index, NULL},
|
||||
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||
NULL}},
|
||||
{
|
||||
.type = http_route_regex,
|
||||
.method = http_get,
|
||||
.path = "^/([^/]+)$",
|
||||
.steps = {lander_get_entry, NULL},
|
||||
.steps_res = {http_loop_step_write_header, lander_stream_body_to_client,
|
||||
NULL},
|
||||
},
|
||||
{
|
||||
.type = http_route_regex,
|
||||
.method = http_delete,
|
||||
.path = "^/([^/]+)$",
|
||||
.steps = {http_loop_step_auth, lander_remove_entry, NULL},
|
||||
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||
NULL},
|
||||
},
|
||||
{
|
||||
.type = http_route_regex,
|
||||
.method = http_post,
|
||||
.path = "^/s(l?)/([^/]*)$",
|
||||
.steps = {http_loop_step_auth, lander_post_redirect,
|
||||
http_loop_step_body_to_buf, lander_post_redirect_body_to_attr,
|
||||
NULL},
|
||||
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||
NULL},
|
||||
},
|
||||
{.type = http_route_regex,
|
||||
.method = http_post,
|
||||
.path = "^/p(l?)/([^/]*)$",
|
||||
.steps = {http_loop_step_auth, http_loop_step_parse_content_length,
|
||||
lander_post_paste, lander_stream_body_to_entry, NULL},
|
||||
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||
NULL}},
|
||||
{.type = http_route_regex,
|
||||
.method = http_post,
|
||||
.path = "^/f(l?)/([^/]*)$",
|
||||
.steps = {http_loop_step_auth, http_loop_step_parse_content_length,
|
||||
lander_post_file, lander_stream_body_to_entry, NULL},
|
||||
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
|
||||
NULL}},
|
||||
};
|
||||
|
||||
void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); }
|
||||
|
||||
lnm_err lander_ctx_init(void **c_ctx, void *gctx) {
|
||||
|
|
Loading…
Reference in New Issue