Add method and route factory.

This commit is contained in:
Pol Henarejos
2026-04-12 12:09:44 +02:00
parent f84b6bed93
commit 7b8d09550a
2 changed files with 129 additions and 102 deletions

View File

@@ -27,9 +27,15 @@ typedef struct {
static struct tcp_pcb *listener_pcb = NULL; static struct tcp_pcb *listener_pcb = NULL;
static rest_conn_t conns[REST_MAX_CONNS]; static rest_conn_t conns[REST_MAX_CONNS];
__attribute__((weak)) const rest_route_t *rest_get_routes(size_t *count) {
if (count != NULL) {
*count = 0;
}
return NULL;
}
static rest_conn_t *alloc_conn(struct tcp_pcb *pcb) { static rest_conn_t *alloc_conn(struct tcp_pcb *pcb) {
size_t i; size_t i;
for (i = 0; i < REST_MAX_CONNS; i++) { for (i = 0; i < REST_MAX_CONNS; i++) {
if (!conns[i].in_use) { if (!conns[i].in_use) {
memset(&conns[i], 0, sizeof(conns[i])); memset(&conns[i], 0, sizeof(conns[i]));
@@ -38,7 +44,6 @@ static rest_conn_t *alloc_conn(struct tcp_pcb *pcb) {
return &conns[i]; return &conns[i];
} }
} }
return NULL; return NULL;
} }
@@ -46,42 +51,34 @@ static void clear_conn(rest_conn_t *conn) {
if (conn == NULL) { if (conn == NULL) {
return; return;
} }
memset(conn, 0, sizeof(*conn)); memset(conn, 0, sizeof(*conn));
} }
static void close_conn(rest_conn_t *conn) { static void close_conn(rest_conn_t *conn) {
err_t err; err_t err;
if (conn == NULL || conn->pcb == NULL) { if (conn == NULL || conn->pcb == NULL) {
clear_conn(conn); clear_conn(conn);
return; return;
} }
tcp_arg(conn->pcb, NULL); tcp_arg(conn->pcb, NULL);
tcp_recv(conn->pcb, NULL); tcp_recv(conn->pcb, NULL);
tcp_sent(conn->pcb, NULL); tcp_sent(conn->pcb, NULL);
tcp_poll(conn->pcb, NULL, 0); tcp_poll(conn->pcb, NULL, 0);
tcp_err(conn->pcb, NULL); tcp_err(conn->pcb, NULL);
err = tcp_close(conn->pcb); err = tcp_close(conn->pcb);
if (err != ERR_OK) { if (err != ERR_OK) {
tcp_abort(conn->pcb); tcp_abort(conn->pcb);
} }
clear_conn(conn); clear_conn(conn);
} }
static void send_response_and_close(rest_conn_t *conn, int status_code, const char *status_text, static void send_response(rest_conn_t *conn, int status_code, const char *status_text, const char *content_type, const char *body, size_t body_len) {
const char *content_type, const char *body, size_t body_len) {
char headers[256]; char headers[256];
int header_len; int header_len;
err_t err; err_t err;
if (conn == NULL || conn->pcb == NULL) { if (conn == NULL || conn->pcb == NULL) {
return; return;
} }
header_len = snprintf(headers, sizeof(headers), header_len = snprintf(headers, sizeof(headers),
"HTTP/1.0 %d %s\r\n" "HTTP/1.0 %d %s\r\n"
"Content-Type: %s\r\n" "Content-Type: %s\r\n"
@@ -93,73 +90,86 @@ static void send_response_and_close(rest_conn_t *conn, int status_code, const ch
close_conn(conn); close_conn(conn);
return; return;
} }
err = tcp_write(conn->pcb, headers, (uint16_t)header_len, TCP_WRITE_FLAG_COPY); err = tcp_write(conn->pcb, headers, (uint16_t)header_len, TCP_WRITE_FLAG_COPY);
if (err == ERR_OK && body_len > 0) { if (err == ERR_OK && body_len > 0) {
err = tcp_write(conn->pcb, body, (uint16_t)body_len, TCP_WRITE_FLAG_COPY); err = tcp_write(conn->pcb, body, (uint16_t)body_len, TCP_WRITE_FLAG_COPY);
} }
if (err == ERR_OK) { if (err == ERR_OK) {
(void)tcp_output(conn->pcb); (void)tcp_output(conn->pcb);
} }
close_conn(conn); close_conn(conn);
} }
static void send_json_and_close(rest_conn_t *conn, int status_code, const char *status_text, const char *json_body) { static void send_json(rest_conn_t *conn, int status_code, const char *status_text, const char *json_body) {
send_response_and_close(conn, status_code, status_text, "application/json", json_body, strlen(json_body)); send_response(conn, status_code, status_text, "application/json", json_body, strlen(json_body));
} }
static int parse_request(rest_conn_t *conn, static const char *status_text_from_code(uint16_t code) {
char *method, switch (code) {
size_t method_size, case 200:
char *path, return "OK";
size_t path_size, case 400:
const char **body, return "Bad Request";
size_t *body_len, case 404:
bool *json_content_type) { return "Not Found";
char *header_end; case 405:
char *line_end; return "Method Not Allowed";
char *cursor; case 413:
return "Payload Too Large";
case 415:
return "Unsupported Media Type";
case 500:
return "Internal Server Error";
default:
return "OK";
}
}
static int parse_request(rest_conn_t *conn, rest_http_method_t *method, char *path, size_t path_size, rest_request_t *request) {
char *header_end, *line_end, *cursor;
size_t headers_size; size_t headers_size;
unsigned long content_length = 0; unsigned long content_length = 0;
char method_str[REST_MAX_METHOD_SIZE] = {0};
*body = NULL; memset(request, 0, sizeof(rest_request_t));
*body_len = 0;
*json_content_type = false;
conn->request[conn->request_len] = '\0'; conn->request[conn->request_len] = '\0';
header_end = strstr(conn->request, "\r\n\r\n"); header_end = strstr(conn->request, "\r\n\r\n");
if (header_end == NULL) { if (header_end == NULL) {
return 0; return 0;
} }
headers_size = (size_t)(header_end - conn->request) + 4; headers_size = (size_t)(header_end - conn->request) + 4;
line_end = strstr(conn->request, "\r\n"); line_end = strstr(conn->request, "\r\n");
if (line_end == NULL || line_end > header_end) { if (line_end == NULL || line_end > header_end) {
return -1; return -1;
} }
*line_end = '\0'; *line_end = '\0';
if (sscanf(conn->request, "%7s %191s", method, path) != 2) { if (sscanf(conn->request, "%7s %191s", method_str, path) != 2) {
return -1;
}
if (strcmp(method_str, "GET") == 0) {
*method = REST_HTTP_GET;
}
else if (strcmp(method_str, "POST") == 0) {
*method = REST_HTTP_POST;
}
else if (strcmp(method_str, "PUT") == 0) {
*method = REST_HTTP_PUT;
}
else if (strcmp(method_str, "DELETE") == 0) {
*method = REST_HTTP_DELETE;
}
else {
return -1; return -1;
} }
cursor = line_end + 2; cursor = line_end + 2;
while (cursor < header_end) { while (cursor < header_end) {
char *next = strstr(cursor, "\r\n"); char *next = strstr(cursor, "\r\n"), *colon, *name, *value, *endptr;
char *colon;
char *name;
char *value;
char *endptr;
if (next == NULL || next > header_end) { if (next == NULL || next > header_end) {
return -1; return -1;
} }
if (next == cursor) { if (next == cursor) {
break; break;
} }
*next = '\0'; *next = '\0';
colon = strchr(cursor, ':'); colon = strchr(cursor, ':');
if (colon != NULL) { if (colon != NULL) {
@@ -185,82 +195,80 @@ static int parse_request(rest_conn_t *conn,
if ((endptr == value) || (*endptr != '\0') || (content_length > REST_MAX_REQUEST_SIZE)) { if ((endptr == value) || (*endptr != '\0') || (content_length > REST_MAX_REQUEST_SIZE)) {
return -1; return -1;
} }
} else if (strcasecmp(name, "Content-Type") == 0) { }
if (strncasecmp(value, "application/json", 16) == 0) { else if (strcasecmp(name, "Content-Type") == 0) {
*json_content_type = true; request->content_type = value;
} }
} }
}
cursor = next + 2; cursor = next + 2;
} }
if (conn->request_len < headers_size + content_length) { if (conn->request_len < headers_size + content_length) {
return 0; return 0;
} }
request->body = conn->request + headers_size;
*body = conn->request + headers_size; request->body_len = (size_t)content_length;
*body_len = (size_t)content_length;
return 1; return 1;
} }
static void handle_request(rest_conn_t *conn) { static void handle_request(rest_conn_t *conn) {
char method[REST_MAX_METHOD_SIZE] = {0}; rest_http_method_t method;
char path[REST_MAX_PATH_SIZE] = {0}; char path[REST_MAX_PATH_SIZE] = {0};
const char *body; rest_request_t request;
size_t body_len; rest_response_t response;
bool is_json; const rest_route_t *routes;
size_t route_count = 0, i;
bool path_exists_for_other_method = false;
int parsed; int parsed;
parsed = parse_request(conn, method, sizeof(method), path, sizeof(path), &body, &body_len, &is_json); parsed = parse_request(conn, &method, path, sizeof(path), &request);
if (parsed <= 0) { if (parsed <= 0) {
if (parsed < 0) { if (parsed < 0) {
send_json_and_close(conn, 400, "Bad Request", "{\"error\":\"bad_request\"}"); send_json(conn, 400, "Bad Request", "{\"error\":\"bad_request\"}");
} }
return; return;
} }
request.method = method;
request.path = path;
routes = rest_get_routes(&route_count);
for (i = 0; i < route_count; i++) {
if (routes[i].path == NULL || routes[i].handler == NULL) {
continue;
}
if (strcmp(routes[i].path, path) != 0) {
continue;
}
if (routes[i].method != method) {
path_exists_for_other_method = true;
continue;
}
response.status_code = 200;
response.content_type = "application/json";
response.body = "{\"ok\":true}";
response.body_len = strlen(response.body);
if (routes[i].handler(&request, &response) != 0) {
send_json(conn, 500, "Internal Server Error", "{\"error\":\"internal_error\"}");
return;
}
if (response.content_type == NULL || response.body == NULL) {
send_json(conn, 500, "Internal Server Error", "{\"error\":\"invalid_response\"}");
return;
}
send_response(conn, response.status_code, status_text_from_code(response.status_code), response.content_type, response.body, response.body_len);
return;
}
if (strcmp(method, "GET") == 0) { if (path_exists_for_other_method) {
if (strcmp(path, "/health") == 0) { send_json(conn, 405, "Method Not Allowed", "{\"error\":\"method_not_allowed\"}");
send_json_and_close(conn, 200, "OK", "{\"ok\":true}"); } else {
return; send_json(conn, 404, "Not Found", "{\"error\":\"not_found\"}");
} }
send_json_and_close(conn, 404, "Not Found", "{\"error\":\"not_found\"}");
return;
}
if (strcmp(method, "POST") == 0 || strcmp(method, "PUT") == 0) {
if (strcmp(path, "/echo") != 0) {
send_json_and_close(conn, 404, "Not Found", "{\"error\":\"not_found\"}");
return;
}
if (!is_json) {
send_json_and_close(conn, 415, "Unsupported Media Type", "{\"error\":\"content_type_must_be_application_json\"}");
return;
}
send_response_and_close(conn, 200, "OK", "application/json", body, body_len);
return;
}
if (strcmp(method, "DELETE") == 0) {
if (strcmp(path, "/echo") == 0) {
send_json_and_close(conn, 200, "OK", "{\"deleted\":true}");
return;
}
send_json_and_close(conn, 404, "Not Found", "{\"error\":\"not_found\"}");
return;
}
send_json_and_close(conn, 405, "Method Not Allowed", "{\"error\":\"method_not_allowed\"}");
} }
static err_t rest_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { static err_t rest_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
rest_conn_t *conn = (rest_conn_t *)arg; rest_conn_t *conn = (rest_conn_t *)arg;
LWIP_UNUSED_ARG(pcb); LWIP_UNUSED_ARG(pcb);
if (err != ERR_OK) { if (err != ERR_OK) {
if (p != NULL) { if (p != NULL) {
pbuf_free(p); pbuf_free(p);
@@ -268,30 +276,25 @@ static err_t rest_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err
close_conn(conn); close_conn(conn);
return err; return err;
} }
if (p == NULL) { if (p == NULL) {
close_conn(conn); close_conn(conn);
return ERR_OK; return ERR_OK;
} }
if (conn == NULL) { if (conn == NULL) {
tcp_recved(pcb, p->tot_len); tcp_recved(pcb, p->tot_len);
pbuf_free(p); pbuf_free(p);
return ERR_OK; return ERR_OK;
} }
if (conn->request_len + p->tot_len > REST_MAX_REQUEST_SIZE) { if (conn->request_len + p->tot_len > REST_MAX_REQUEST_SIZE) {
tcp_recved(pcb, p->tot_len); tcp_recved(pcb, p->tot_len);
pbuf_free(p); pbuf_free(p);
send_json_and_close(conn, 413, "Payload Too Large", "{\"error\":\"payload_too_large\"}"); send_json(conn, 413, "Payload Too Large", "{\"error\":\"payload_too_large\"}");
return ERR_OK; return ERR_OK;
} }
pbuf_copy_partial(p, conn->request + conn->request_len, p->tot_len, 0); pbuf_copy_partial(p, conn->request + conn->request_len, p->tot_len, 0);
conn->request_len += p->tot_len; conn->request_len += p->tot_len;
tcp_recved(pcb, p->tot_len); tcp_recved(pcb, p->tot_len);
pbuf_free(p); pbuf_free(p);
handle_request(conn); handle_request(conn);
return ERR_OK; return ERR_OK;
} }
@@ -311,54 +314,44 @@ static void rest_err(void *arg, err_t err) {
static err_t rest_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { static err_t rest_accept(void *arg, struct tcp_pcb *newpcb, err_t err) {
rest_conn_t *conn; rest_conn_t *conn;
LWIP_UNUSED_ARG(arg); LWIP_UNUSED_ARG(arg);
if (err != ERR_OK || newpcb == NULL) { if (err != ERR_OK || newpcb == NULL) {
if (newpcb != NULL) { if (newpcb != NULL) {
tcp_abort(newpcb); tcp_abort(newpcb);
} }
return ERR_ABRT; return ERR_ABRT;
} }
conn = alloc_conn(newpcb); conn = alloc_conn(newpcb);
if (conn == NULL) { if (conn == NULL) {
tcp_abort(newpcb); tcp_abort(newpcb);
return ERR_ABRT; return ERR_ABRT;
} }
tcp_arg(newpcb, conn); tcp_arg(newpcb, conn);
tcp_recv(newpcb, rest_recv); tcp_recv(newpcb, rest_recv);
tcp_poll(newpcb, rest_poll, 8); tcp_poll(newpcb, rest_poll, 8);
tcp_err(newpcb, rest_err); tcp_err(newpcb, rest_err);
return ERR_OK; return ERR_OK;
} }
err_t rest_server_init(void) { err_t rest_server_init(void) {
err_t err; err_t err;
if (listener_pcb != NULL) { if (listener_pcb != NULL) {
return ERR_OK; return ERR_OK;
} }
listener_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); listener_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
if (listener_pcb == NULL) { if (listener_pcb == NULL) {
return ERR_MEM; return ERR_MEM;
} }
err = tcp_bind(listener_pcb, IP_ANY_TYPE, REST_PORT); err = tcp_bind(listener_pcb, IP_ANY_TYPE, REST_PORT);
if (err != ERR_OK) { if (err != ERR_OK) {
tcp_abort(listener_pcb); tcp_abort(listener_pcb);
listener_pcb = NULL; listener_pcb = NULL;
return err; return err;
} }
listener_pcb = tcp_listen_with_backlog(listener_pcb, REST_MAX_CONNS); listener_pcb = tcp_listen_with_backlog(listener_pcb, REST_MAX_CONNS);
if (listener_pcb == NULL) { if (listener_pcb == NULL) {
return ERR_MEM; return ERR_MEM;
} }
tcp_accept(listener_pcb, rest_accept); tcp_accept(listener_pcb, rest_accept);
return ERR_OK; return ERR_OK;
} }

View File

@@ -2,6 +2,40 @@
#define PICO_KEYS_REST_SERVER_H #define PICO_KEYS_REST_SERVER_H
#include "lwip/err.h" #include "lwip/err.h"
#include <stddef.h>
#include <stdint.h>
typedef enum {
REST_HTTP_GET = 0,
REST_HTTP_POST,
REST_HTTP_PUT,
REST_HTTP_DELETE
} rest_http_method_t;
typedef struct {
rest_http_method_t method;
const char *path;
const char *body;
size_t body_len;
const char *content_type;
} rest_request_t;
typedef struct {
uint16_t status_code;
const char *content_type;
const char *body;
size_t body_len;
} rest_response_t;
typedef int (*rest_route_handler_t)(const rest_request_t *request, rest_response_t *response);
typedef struct {
rest_http_method_t method;
const char *path;
rest_route_handler_t handler;
} rest_route_t;
const rest_route_t *rest_get_routes(size_t *count);
err_t rest_server_init(void); err_t rest_server_init(void);