Improve path resolution for server using a tree, add an example.
authorNot Zed <notzed@gmail.com>
Wed, 9 Jun 2021 00:06:24 +0000 (09:36 +0930)
committerNot Zed <notzed@gmail.com>
Wed, 9 Jun 2021 00:06:24 +0000 (09:36 +0930)
Makefile
ez-http.c
ez-http.h
test-http.c [new file with mode: 0644]

index 94348f5..b462a2f 100644 (file)
--- 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=)
 
index 1e8ae5b..f0a36ac 100644 (file)
--- a/ez-http.c
+++ b/ez-http.c
@@ -33,7 +33,7 @@
 #include <stdlib.h>
 #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;i<count;i++)
-               ez_set_put(&s->handlers, &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");
index 52632a2..b936d89 100644 (file)
--- a/ez-http.h
+++ b/ez-http.h
  * 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 (file)
index 0000000..5fad509
--- /dev/null
@@ -0,0 +1,88 @@
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+
+#include <stdlib.h>
+#include "ez-blob-io.h"
+#include "ez-list.h"
+#include "ez-set.h"
+
+#define obstack_chunk_alloc malloc
+#define obstack_chunk_free free
+#include <obstack.h>
+#include <stdio.h>
+#include <errno.h>
+#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 = "<YAMAHA_AV cmd=\"PUT\"><Main_Zone><Volume><Lvl><Val>-350</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume></Main_Zone></YAMAHA_AV>";
+       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 = "<h1>It Works!</h1><a href='/quit'>Quit</a> | <a href='/'>Player</a>";
+
+       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;
+}