From 595107041d31492aee731d00667383259e16a996 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Wed, 9 Jun 2021 09:36:24 +0930 Subject: [PATCH] Improve path resolution for server using a tree, add an example. --- Makefile | 2 +- ez-http.c | 45 ++++++++++++++------------- ez-http.h | 22 ++++++++++---- test-http.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 test-http.c diff --git a/Makefile b/Makefile index 94348f5..b462a2f 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ HEADERS = \ ez-set.h \ ez-tree.h -test_SRCS=test-bitset.c test-blob.c test-port.c test-set.c test-tree.c test-list.c +test_SRCS=test-bitset.c test-blob.c test-port.c test-set.c test-tree.c test-list.c test-http.c TESTS=$(test_SRCS:.c=) diff --git a/ez-http.c b/ez-http.c index 1e8ae5b..f0a36ac 100644 --- a/ez-http.c +++ b/ez-http.c @@ -33,7 +33,7 @@ #include #include "ez-blob-io.h" #include "ez-list.h" -#include "ez-set.h" +#include "ez-tree.h" #define obstack_chunk_alloc malloc #define obstack_chunk_free free @@ -314,14 +314,6 @@ static int parse_request_status(struct ez_httprequest *r) { return 0; } -static unsigned handler_hash(const void *a) { - return ez_hash_string(((struct ez_httphandler *)a)->path); -} - -static int handler_equals(const void *a, const void *b) { - return strcmp(((struct ez_httphandler *)a)->path, ((struct ez_httphandler *)b)->path) == 0; -} - // read all data, data may be null (== noop) int httpconnection_read(struct ez_httpconnection *s, struct ez_blobio *data) { if (data) { @@ -350,9 +342,9 @@ int httpconnection_write(struct ez_httpconnection *s, struct ez_blobio *data) { while (len > 0 && (len = write(s->fd, data->data + data->index, len)) >= 0) { - printf("send:\n"); - fwrite(data->data + data->index, 1, len, stdout); - printf("\n--\n"); + //printf("send:\n"); + //fwrite(data->data + data->index, 1, len, stdout); + //printf("\n--\n"); data->index += len; len = data->size - data->index; } @@ -369,14 +361,15 @@ static int read_http(struct ez_http *http) { do { ssize_t len = read(s->fd, s->io.data, s->io.alloc); - if (len < 0) - return -1; + if (len <= 0) + break; + s->io.index = 0; s->io.size = len; - printf("read:\n"); - fwrite(s->io.data, 1, len, stdout); - printf("\n--\n"); + //printf("read:\n"); + //fwrite(s->io.data, 1, len, stdout); + //printf("\n--\n"); res = parse_http(http, &s->io); if (res == 2) @@ -432,7 +425,7 @@ static int httpresponse_run(struct ez_httpresponse *r) { int httpserver_init(struct ez_httpserver *s, int port) { memset(s, 0, sizeof(*s)); - ez_set_init(&s->handlers, handler_hash, handler_equals, NULL); + ez_tree_init(&s->handlers, ez_string_node_cmp); memset(&s->addr, 0, sizeof(s->addr)); s->addr.sin_family = AF_INET; @@ -443,13 +436,12 @@ int httpserver_init(struct ez_httpserver *s, int port) { } void httpserver_free(struct ez_httpserver *s) { - ez_set_clear(&s->handlers); + // all static alloc } int httpconnection_init(struct ez_httpconnection *conn, struct ez_httpserver *s) { memset(conn, 0, sizeof(*conn)); - conn->server = s; conn->io.alloc = 4096; conn->io.data = malloc(conn->io.alloc); conn->io.mode = BLOBIO_READ; @@ -474,7 +466,7 @@ void httpconnection_free(struct ez_httpconnection *conn) { void httpserver_addhandlers(struct ez_httpserver *s, struct ez_httphandler *list, int count) { for (int i=0;ihandlers, &list[i]); + ez_tree_put(&s->handlers, &list[i]); } int httpserver_run(struct ez_httpserver *s) { @@ -531,9 +523,16 @@ int httpserver_run(struct ez_httpserver *s) { res = parse_request_status(&req); if (res == 0) { const struct ez_httphandler key = { .path = req.url }; - const struct ez_httphandler *handler = ez_set_get(&s->handlers, &key); + ez_tree_scan scan; + struct ez_httphandler *handler = ez_tree_scan_init_key(&s->handlers, &scan, EZ_LINK_RIGHT, EZ_LINK_LEFT, &key); + + if (handler && handler->mode == EZ_PATH_EXACT && strcmp(req.url, handler->path) != 0) + handler = NULL; + + if (handler && handler->mode == EZ_PATH_PREFIX && strncmp(req.url, handler->path, strlen(handler->path)) != 0) + handler = NULL; - if (!handler) + if (handler) quit = handler->fn(&req, &rep); else httpresponse_set_response(&rep, 404, "Missing"); diff --git a/ez-http.h b/ez-http.h index 52632a2..b936d89 100644 --- a/ez-http.h +++ b/ez-http.h @@ -27,6 +27,12 @@ * Requests and responses are all in-memory. * All operations are synchronous. * Server is single threaded. + * + * Memory is managed using obstacks so each request/response is + * cleared after use. + * + * You need to define _GNU_SOURCE and various things for obstacks + * and the types to include this file. */ /** @@ -69,14 +75,12 @@ struct ez_httpresponse { * HTTP connection, used for both client and server endpoints. */ struct ez_httpconnection { - struct ez_httpserver *server; - - struct obstack os; + struct obstack os; // Use for building responses and other per-request objects void *empty; - struct sockaddr_in addr; + struct sockaddr_in addr; // remote address of connection + struct ez_blobio io; // used for buffering i/o for headers int fd; - struct ez_blobio io; }; /** @@ -116,12 +120,17 @@ void httpresponse_set_response(struct ez_httpresponse *r, int code, const char * * HTTP server listening socket and state. */ struct ez_httpserver { - ez_set handlers; // set of struct ez_httphandler + ez_tree handlers; // tree of struct ez_httphandler struct sockaddr_in addr; int fd; }; +enum ez_httphandler_t { + EZ_PATH_EXACT, // exact match for url + EZ_PATH_PREFIX // just match prefix provided +}; + /** * Handler for a given url path. * @@ -133,6 +142,7 @@ struct ez_httphandler { ez_node sn; char *path; + enum ez_httphandler_t mode; int (*fn)(struct ez_httprequest *req, struct ez_httpresponse *rep); }; diff --git a/test-http.c b/test-http.c new file mode 100644 index 0000000..5fad509 --- /dev/null +++ b/test-http.c @@ -0,0 +1,88 @@ + +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include "ez-blob-io.h" +#include "ez-list.h" +#include "ez-set.h" + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include +#include +#include +#define D(x) + +#include "ez-http.h" + +// copy to use +const static struct ez_pair ct_text_html = { + .name = "Content-Type", + .value = "text/html;charset=utf-8" +}; +const static struct ez_pair ct_text_xml = { + .name = "Content-Type", + .value = "text/xml" +}; + +void demo_client(void) { + const char *cmd = "-3501dB"; + struct ez_httpclient c; + struct ez_pair h0 = ct_text_xml; + + httpclient_init(&c, "amplifier", 80); + + http_addheaders(&c.request.http, &h0, 1); + http_set_data(&c.request.http, (char *)cmd, strlen(cmd)); + + httpclient_run(&c, "POST", "/YamahaRemoteControl/ctrl"); + + printf("response: %s\n", c.response.http.status); + for (struct ez_pair *w = ez_list_head(&c.response.http.headers), *n = ez_node_succ(w);n;w=n,n = ez_node_succ(w)) + printf("%s:%s\n", w->name, w->value); + printf("payload:\n"); + fwrite(c.response.http.data.data + c.response.http.data.index, 1, c.response.http.data.size - c.response.http.data.index, stdout); + printf("\n--\n"); + + httpclient_free(&c); +} + +static int handle_quit(struct ez_httprequest *r, struct ez_httpresponse *rep) { + httpresponse_set_response(rep, 200, "Byte"); + return 1; +} + +static int handle_root(struct ez_httprequest *req, struct ez_httpresponse *rep) { + struct ez_pair h0 = ct_text_html; + const char *msg = "

It Works!

Quit | Player"; + + httpresponse_set_response(rep, 200, "Ok"); + ez_list_addtail(&rep->http.headers, &h0); + http_set_data(&rep->http, msg, strlen(msg)); + + return 0; +} + +static struct ez_httphandler handler_list[] = { + { .path = "/index.html", .fn = handle_root }, + { .path = "/quit", .fn = handle_quit }, +}; + +void demo_server(void) { + struct ez_httpserver serv; + + httpserver_init(&serv, 8081); + httpserver_addhandlers(&serv, handler_list, 2); + httpserver_run(&serv); + httpserver_free(&serv); +} + +int main(int argc, char **argv) { + demo_client(); + //demo_server(); + return 0; +} -- 2.39.5