Added minimal embedded http client and server.
authorNot Zed <notzed@gmail.com>
Mon, 4 Jan 2021 02:37:30 +0000 (13:07 +1030)
committerNot Zed <notzed@gmail.com>
Mon, 4 Jan 2021 02:37:30 +0000 (13:07 +1030)
Minor tweaks to blob compiler.
Added some specific node types to ez-node.

.gitignore
Makefile
ez-blob-compiler.c
ez-blob-io.h
ez-http.c [new file with mode: 0644]
ez-http.h [new file with mode: 0644]
ez-node.h
ez-set.h
ez-tree.h

index 1905831..d8f1701 100644 (file)
@@ -1,2 +1,4 @@
 .deps
 libeze.a
+*.o
+
index 115646f..94348f5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
 
 CPPFLAGS=-I.
-CFLAGS=-O2 -g -fPIC -Wall -mtune=native
+#CFLAGS=-O2 -g -fPIC -Wall -mtune=native
+CFLAGS=-O0 -g -fPIC -Wall
 test_LDLIBS=-lpthread -lrt
 ARFLAGS=rvsUc
 VERSION=2.1.99
@@ -16,6 +17,7 @@ SRCS=                                         \
  ez-blob-tagz.c                                        \
  ez-blob-xdrn.c                                        \
  ez-elf.c                                      \
+ ez-http.c                                     \
  ez-port.c                                     \
  ez-set.c                                      \
  ez-tree.c
@@ -28,6 +30,7 @@ HEADERS =                                     \
  ez-blob-tagz.h                                        \
  ez-blob-xdrn.h                                        \
  ez-elf.h                                      \
+ ez-http.h                                     \
  ez-list.h                                     \
  ez-node.h                                     \
  ez-port.h                                     \
index 2050d3f..cb9a41f 100644 (file)
@@ -161,18 +161,18 @@ void export_encode(const ez_blob_desc *desc, const char *name) {
                        abort();
                }
        }
-       printf("\tif (b - blob->eb_data != blob->eb_size) abort();\n");
+       printf("\tif (b - (char *)blob->eb_data != blob->eb_size) abort();\n");
        printf("}\n");
 }
 
 void export_decode(const ez_blob_desc *desc, const char *name, int doos) {
        if (doos) {
-               printf("void *%s_decode_os(const ez_blob * __restrict ez_blob *blob, struct obstack * __restrict os) {\n", name);
+               printf("void *%s_decode_os(const ez_blob * __restrict blob, struct obstack * __restrict os) {\n", name);
                printf("\tvoid *p = obstack_alloc(os, %u);\n", desc->bd_offset);
        } else {
                printf("void %s_decode_raw(const ez_blob * __restrict blob, void * __restrict p) {\n", name);
        }
-       printf("\tconst char * __restrict b = blob.eb_data;\n");
+       printf("\tconst char * __restrict b = blob->eb_data;\n");
        printf("\tsize_t len;\n");
        printf("\tchar **sp;\n");
        if (doos)
@@ -224,7 +224,7 @@ void export_decode(const ez_blob_desc *desc, const char *name, int doos) {
                        abort();
                }
        }
-       printf("\tif (b - blob->eb_data != blob->eb_size) abort();\n");
+       printf("\tif (b - (const char *)blob->eb_data != blob->eb_size) abort();\n");
        if (doos)
                printf("\treturn p;\n");
        printf("}\n");
@@ -286,10 +286,9 @@ int main(int argc, char **argv) {
 
        if (!doinline && !header) {
                printf("#include <stdint.h>\n");
-               printf("#include <libeze/ez-blob.h>\n");
+               printf("#include <ez-blob.h>\n");
                printf("#include <string.h>\n");
                printf("#include <stdlib.h>\n");
-
                // should just be another link file i suppose
                printf("static char *string_encode(char * __restrict b, const char *s) {\n"
                       "\tif (s) {\n"
@@ -358,7 +357,7 @@ int main(int argc, char **argv) {
                        if (gen & 4)
                                printf("void %s_decode_raw(const ez_blob *blob, void *p);\n", xname);
                        if (gen & 8)
-                               printf("void *%s_decode_os(const ez_blob *blob, struct obstack *os)\n", xname);
+                               printf("void *%s_decode_os(const ez_blob *blob, struct obstack *os);\n", xname);
                } else {
                        if (gen & 1)
                                export_size(desc, xname);
index 8e07197..1c01620 100644 (file)
@@ -33,6 +33,8 @@ struct ez_blobio {
        // for read: upper limit
        // for write: allocation size
        size_t size;
+       // total allocation size
+       size_t alloc;
 
        // on read, non-zero means invalid
        int error;
diff --git a/ez-http.c b/ez-http.c
new file mode 100644 (file)
index 0000000..1e8ae5b
--- /dev/null
+++ b/ez-http.c
@@ -0,0 +1,657 @@
+/* ez-http.c: HTTP processing support routines.
+
+  Copyright (C) 2021 Michael Zucchi
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see
+  <http://www.gnu.org/licenses/>.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <ctype.h>
+#include <sys/uio.h>
+#include <netdb.h>
+#include <signal.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"
+
+static void blobio_init_read(struct ez_blobio *io, const void *data, size_t size) {
+       io->index = 0;
+       io->data = (void *)data;
+       io->size = size;
+       io->alloc = 0;
+       io->error = 0;
+       io->mode = BLOBIO_READ;
+}
+
+static void init_request(struct ez_httprequest *r, struct ez_httpconnection *c) {
+       memset(r, 0, sizeof(*r));
+
+       r->http.conn = c;
+       ez_list_init(&r->http.headers);
+       ez_list_init(&r->params);
+}
+
+static void init_response(struct ez_httpresponse *r, struct ez_httpconnection *c) {
+       memset(r, 0, sizeof(*r));
+
+       r->http.conn = c;
+       ez_list_init(&r->http.headers);
+}
+
+void httpresponse_set_response(struct ez_httpresponse *r, int code, const char *msg) {
+       r->code = code;
+       r->message = obstack_copy(&r->http.conn->os, msg, strlen(msg)+1);
+}
+
+/*
+  Call repeatedly to scan headers
+
+  This swallows all of the data in io each call, either copying it to the headers
+  or to http.data
+
+  states of return is:
+    1* (data.size  = 0)
+    2  (data.size >= 0)
+    1* (data.size >= 0)
+
+  return -1 error
+  return  0 finished
+  return  1 call again
+  return  2 headers complete, may include start of data in http.data
+ */
+
+enum {
+       STATE_HEADER,
+       STATE_CR,
+       STATE_CONTENT,
+};
+
+static int parse_http(struct ez_http *r, struct ez_blobio * __restrict io) {
+       struct obstack *os = &r->conn->os;
+
+       while (r->state != STATE_CONTENT) {
+               size_t size = io->size - io->index;
+               const uint8_t *s = memchr(io->data + io->index, '\n', size);
+               if (s) {
+                       size_t len = s - (io->data + io->index);
+
+                       if (len > 0 && s[-1] == '\r')
+                               obstack_grow0(os, blobio_take(io, len + 1), len - 1);
+                       else if (len == 0 && r->state == STATE_CR)
+                               obstack_grow0(os, blobio_take(io, 1), 0);
+                       else
+                               return -1;
+
+                       len = obstack_object_size(os) - 1;
+                       if (len) {
+                               char *value = obstack_finish(os);
+
+                               if (!value)
+                                       return -1;
+
+                               if (!r->status) {
+                                       r->status = value;
+                               } else {
+                                       struct ez_pair *h = obstack_alloc(os, sizeof(*h));
+
+                                       if (!h)
+                                               return -1;
+
+                                       h->name = value;
+                                       h->value = strchr(value, ':');
+                                       if (h->value) {
+                                               *(h->value)++ = 0;
+                                               if (strcmp(h->name, "Content-Length") == 0)
+                                                       r->content_length = strtoul(h->value, NULL, 10);
+                                       } else {
+                                               // error?
+                                       }
+                                       ez_list_addtail(&r->headers, h);
+                               }
+                               r->state = STATE_HEADER;
+                       } else {
+                               // loses a byte, ignore
+                               obstack_finish(os);
+
+                               r->state = STATE_CONTENT;
+
+                               // new 'read-all-in' version
+                               r->data.index = 0;
+                               r->data.data = obstack_alloc(os, r->content_length);
+                               if (!r->data.data)
+                                       return -1;
+                               r->data.size = r->content_length;
+                               r->data.alloc = r->content_length;
+                               r->data.mode = BLOBIO_READ;
+
+                               // swallow all we have up to limit
+                               size_t size = io->size - io->index;
+                               size_t max = r->data.size - r->data.index;
+                               size_t len = size > max ? max : size;
+
+                               memcpy(r->data.data + r->data.index, blobio_take(io, len), len);
+                               r->data.index += len;
+                       }
+               } else {
+                       if (io->size > 0 && io->index < io->size && io->data[io->size-1] == '\r') {
+                               r->state = STATE_CR;
+                               obstack_grow(os, blobio_take(io, size), size-1);
+                       } else {
+                               r->state = STATE_HEADER;
+                               obstack_grow(os, blobio_take(io, size), size);
+                       }
+                       return 1;
+               }
+       }
+
+       return 2;
+}
+
+// this assumes the nybble is a valid char, worst that happens is you get junk
+static int nybble(int c) {
+       if (c <= '9')
+               return c - '0';
+       else if (c <= 'F')
+               return c - 'A' + 10;
+       else
+               return c - 'a' + 10;
+}
+
+// url decode in-place
+static void url_decode(char *s) {
+       char *o = s;
+       char c;
+
+       while ((c = *s++)) {
+               if (c == '%' && s[0] && s[1]) {
+                       c = (nybble(s[0]) << 4) | nybble(s[1]);
+                       s += 2;
+               } else if (c == '+')
+                       c = ' ';
+               *o++ = c;
+       }
+       *o = 0;
+}
+
+static int tohex(int nybble) {
+       return nybble < 10 ? nybble + '0' : nybble + 'a' - 10;
+}
+
+// see tables.c
+#define UCHAR 0x01
+#define HEX 0x02
+const uint8_t type[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static __inline__ int is_uchar(char c) {
+       return (type[(unsigned)c] & UCHAR) != 0;
+}
+
+static void url_encode(struct obstack *os, const char *s) {
+       char c;
+       // TODO: utf8?
+       while ((c = *s++)) {
+               if (is_uchar(c))
+                       obstack_1grow(os, c);
+               else if (c == ' ')
+                       obstack_1grow(os, '+');
+               else {
+                       obstack_1grow(os, '%');
+                       obstack_1grow(os, tohex((c>>4)&0x0f));
+                       obstack_1grow(os, tohex(c&0x0f));
+               }
+       }
+}
+
+// parse response VERSION CODE REASON
+static int parse_response_status(struct ez_httpresponse *r) {
+       char *code, *message;
+       code = strchr(r->http.status, ' ');
+       if (code) {
+               *code++ = 0;
+               message = strchr(code, ' ');
+               if (message)
+                       *message++ = 0;
+               else
+                       return -1;
+       } else
+               return -1;
+
+       r->version = r->http.status;
+       r->code = atoi(code);
+       r->message = message;
+       return 0;
+}
+
+// parse request METHOD URL?params VERSION
+static int parse_request_status(struct ez_httprequest *r) {
+       char *url, *version;
+       url = strchr(r->http.status, ' ');
+       if (url) {
+               *url++ = 0;
+               version = strchr(url, ' ');
+               if (version)
+                       *version++ = 0;
+               else
+                       return -1;
+       } else
+               return -1;
+
+       r->method = r->http.status;
+       r->url = url;
+       r->version = version;
+
+       // parse the url parameters if there are any
+       // this overwwirtes r->url[contents]
+       char *s = strchr(url, '?'), *t, *u;
+       if (s) {
+               struct obstack *os = &r->http.conn->os;
+
+               *s++ = 0;
+               do {
+                       t = strchr(s, '&');
+                       if (t) *t++ = 0;
+                       u = strchr(s, '=');
+                       if (u) {
+                               *u++ = 0;
+                               url_decode(u);
+                       }
+                       url_decode(s);
+
+                       struct ez_pair *h = obstack_alloc(os, sizeof(*h));
+                       h->name = s;
+                       h->value = u;
+                       ez_list_addtail(&r->params, h);
+                       s = t;
+               } while (t);
+       }
+       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) {
+               ssize_t len = data->size - data->index;
+
+               while (len > 0
+                       && (len = read(s->fd, data->data + data->index, len)) >= 0) {
+                       printf("read:\n");
+                       fwrite(data->data + data->index, 1, len, stdout);
+                       printf("\n--\n");
+                       data->index += len;
+                       len = data->size - data->index;
+               }
+               // reset index for reading
+               data->index = 0;
+               return len;
+       } else
+               return 0;
+}
+
+// write all data, data may be null (== noop)
+// return 0 on success
+int httpconnection_write(struct ez_httpconnection *s, struct ez_blobio *data) {
+       if (data) {
+               ssize_t len = data->size - data->index;
+
+               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");
+                       data->index += len;
+                       len = data->size - data->index;
+               }
+               return len;
+       } else
+               return 0;
+}
+
+// read headers and all data into memory
+static int read_http(struct ez_http *http) {
+       struct ez_httpconnection *s = http->conn;
+       int res;
+
+       do {
+               ssize_t len = read(s->fd, s->io.data, s->io.alloc);
+
+               if (len < 0)
+                       return -1;
+               s->io.index = 0;
+               s->io.size = len;
+
+               printf("read:\n");
+               fwrite(s->io.data, 1, len, stdout);
+               printf("\n--\n");
+
+               res = parse_http(http, &s->io);
+               if (res == 2)
+                       return httpconnection_read(s, &http->data);
+       } while (res > 0);
+       return -1;
+}
+
+// Format and write headers
+static int write_headers(struct ez_http *http) {
+       struct obstack *os = &http->conn->os;
+       struct ez_blobio msg = { 0 };
+       int res;
+
+       http->content_length = http->data.size - http->data.index;
+
+       for (struct ez_pair *w = ez_list_head(&http->headers), *n = ez_node_succ(w);n;w=n,n = ez_node_succ(w))
+               obstack_printf(os, "%s:%s\r\n", w->name, w->value);
+       obstack_printf(os, "Content-Length:%zd\r\n\r\n", http->content_length);
+
+       msg.size = obstack_object_size(os);
+       msg.data = obstack_finish(os);
+       if (msg.data) {
+               res = httpconnection_write(http->conn, &msg);
+               obstack_free(os, msg.data);
+       } else {
+               const char *oom = "HTTP/1.0 500 Out of Memory";
+
+               msg.size = strlen(oom);
+               msg.data = (void *)oom;
+
+               httpconnection_write(http->conn, &msg);
+
+               res = -1;
+       }
+
+       return res;
+}
+
+// Send a response
+static int httpresponse_run(struct ez_httpresponse *r) {
+       struct obstack *os = &r->http.conn->os;
+       int res;
+
+       obstack_printf(os, "HTTP/1.0 %d %s\r\n", r->code, r->message);
+       res = write_headers(&r->http);
+       if (res == 0)
+               res = httpconnection_write(r->http.conn, &r->http.data);
+
+       return res;
+}
+
+int httpserver_init(struct ez_httpserver *s, int port) {
+       memset(s, 0, sizeof(*s));
+
+       ez_set_init(&s->handlers, handler_hash, handler_equals, NULL);
+
+       memset(&s->addr, 0, sizeof(s->addr));
+       s->addr.sin_family = AF_INET;
+       s->addr.sin_addr.s_addr = INADDR_ANY;
+       s->addr.sin_port = htons(port);
+
+       return 0;
+}
+
+void httpserver_free(struct ez_httpserver *s) {
+       ez_set_clear(&s->handlers);
+}
+
+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;
+
+       obstack_init(&conn->os);
+       conn->empty = obstack_alloc(&conn->os, 0);
+
+       return 0;
+}
+
+void httpconnection_clear(struct ez_httpconnection *conn) {
+       // close fd?
+       obstack_free(&conn->os, conn->empty);
+       conn->empty = obstack_alloc(&conn->os, 0);
+}
+
+void httpconnection_free(struct ez_httpconnection *conn) {
+       obstack_free(&conn->os, NULL);
+       free(conn->io.data);
+       memset(conn, 0, sizeof(*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]);
+}
+
+int httpserver_run(struct ez_httpserver *s) {
+       int res;
+
+       signal(SIGPIPE, SIG_IGN);
+
+       s->fd = socket(AF_INET, SOCK_STREAM, 0);
+       if (s->fd < 0) {
+               perror("ERROR opening socket");
+               return -1;
+       }
+
+       res = 1;
+       res = setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(int));
+
+       res = bind(s->fd, (struct sockaddr *)&s->addr, sizeof(s->addr));
+       if (res != 0)
+               goto fail;
+
+       struct ez_httpconnection conn;
+
+       httpconnection_init(&conn, s);
+       int quit = 0;
+
+       while (!quit) {
+               socklen_t alen = sizeof(conn.addr);
+               struct ez_httprequest req;
+               struct ez_httpresponse rep;
+
+               // Wait for new connection
+               res = listen(s->fd, 16);
+               conn.fd = accept(s->fd, (struct sockaddr *)&conn.addr, &alen);
+
+               if (1) {
+                       struct timeval timeout;
+                       timeout.tv_sec = 5;
+                       timeout.tv_usec = 0;
+
+                       setsockopt(conn.fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
+                       setsockopt(conn.fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));
+               }
+
+               // Default response
+               init_response(&rep, &conn);
+               rep.code = 500;
+               rep.message = "Server Error";
+
+               // Read headers and data, and map to handlers
+               init_request(&req, &conn);
+               res = read_http(&req.http);
+
+               if (res == 0) {
+                       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);
+
+                               if (!handler)
+                                       quit = handler->fn(&req, &rep);
+                               else
+                                       httpresponse_set_response(&rep, 404, "Missing");
+                       } else
+                               httpresponse_set_response(&rep, 400, "Invalid");
+               } else
+                       httpresponse_set_response(&rep, 400, "Invalid");
+
+               httpresponse_run(&rep);
+
+               close(conn.fd);
+
+               httpconnection_clear(&conn);
+       }
+       httpconnection_free(&conn);
+       res = 0;
+fail:
+       signal(SIGPIPE, SIG_DFL);
+
+       close(s->fd);
+       s->fd = -1;
+
+       return res;
+}
+
+int httpclient_init(struct ez_httpclient *cc, const char *host, int port) {
+       struct hostent *server;
+       struct ez_httpconnection *c = &cc->conn;
+
+       httpconnection_init(c, NULL);
+
+       server = gethostbyname(host);
+       memset(&c->addr, 0, sizeof(c->addr));
+       c->addr.sin_family = AF_INET;
+       c->addr.sin_port = htons(port);
+       memcpy(&c->addr.sin_addr.s_addr, server->h_addr, server->h_length);
+
+       init_request(&cc->request, &cc->conn);
+
+       return 0;
+}
+
+void httpclient_free(struct ez_httpclient *cc) {
+       httpconnection_free(&cc->conn);
+}
+
+void httprequest_addparams(struct ez_httprequest *h, struct ez_pair *list, int count) {
+       for (int i=0;i<count;i++)
+               ez_list_addtail(&h->params, &list[i]);
+}
+
+void http_addheaders(struct ez_http *http, struct ez_pair *list, int count) {
+       for (int i=0;i<count;i++)
+               ez_list_addtail(&http->headers, &list[i]);
+}
+
+void http_set_data(struct ez_http *http, const char *data, size_t size) {
+       blobio_init_read(&http->data, data, size);
+}
+
+// simple send/receive in-memory version
+// send request header and all data
+// read response header and all data
+int httpclient_run(struct ez_httpclient *cc, const char *method, const char *url) {
+       int res;
+       struct ez_httpconnection *c = &cc->conn;
+       struct ez_httprequest *req = &cc->request;
+       struct ez_httpresponse *rep = &cc->response;
+       struct obstack *os = &c->os;
+
+       c->fd = socket(AF_INET, SOCK_STREAM, 0);
+       res = connect(c->fd, (struct sockaddr *)&c->addr, sizeof(c->addr));
+
+       if (1) {
+               struct timeval timeout;
+               timeout.tv_sec = 5;
+               timeout.tv_usec = 0;
+
+               setsockopt(c->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
+               setsockopt(c->fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));
+       }
+
+       // from request status line, method, url
+       req->method = obstack_copy(os, method, strlen(method)+1);
+       req->url = obstack_copy(os, url, strlen(url)+1);
+
+       obstack_printf(os, "%s %s", method, url);
+       char next = '?';
+
+       // add encoded parameters
+       for (struct ez_pair *w = ez_list_head(&req->params), *n = ez_node_succ(w);n;w=n,n = ez_node_succ(w)) {
+               obstack_1grow(os, next);
+               url_encode(os, w->name);
+               if (w->value) {
+                       obstack_1grow(os, '=');
+                       url_encode(os, w->value);
+               }
+               next = '&';
+       }
+       // and version
+       obstack_printf(os, " HTTP/1.0\r\n");
+
+       // write headers and data
+       res = write_headers(&req->http);
+       if (res == 0) {
+               res = httpconnection_write(c, &req->http.data);
+
+               if (res == 0) {
+                       // fully read response
+                       init_response(rep, req->http.conn);
+                       res = read_http(&rep->http);
+                       if (res == 0)
+                               res = parse_response_status(rep);
+               }
+       }
+
+       close(c->fd);
+       c->fd = -1;
+
+       return res;
+}
diff --git a/ez-http.h b/ez-http.h
new file mode 100644 (file)
index 0000000..52632a2
--- /dev/null
+++ b/ez-http.h
@@ -0,0 +1,171 @@
+/* ez-http.c: HTTP processing support routines.
+
+  Copyright (C) 2021 Michael Zucchi
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see
+  <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef EZ_HTTP_H
+#define EZ_HTTP_H
+
+/**
+ * This implements a trivial HTTP/1.0 server and client for simple
+ * embedded use.
+ *
+ * Requests and responses are all in-memory.
+ * All operations are synchronous.
+ * Server is single threaded.
+ */
+
+/**
+ * Common parts of request and response
+ */
+struct ez_http {
+       struct ez_httpconnection *conn;
+       char *status;           // raw status line before decoding
+       struct ez_list headers; // struct ez_pair headers
+       size_t content_length;
+       struct ez_blobio data;
+       int state;              // internal decode state
+};
+
+/**
+ * Request specific fields, decoded status line and url params.
+ */
+struct ez_httprequest {
+       struct ez_http http;
+
+       char *method;
+       char *url;              // base url up to ?
+       char *version;
+
+       struct ez_list params;  // decoded params struct ez_pair
+};
+
+/**
+ * Response specific fields, decoded status line.
+ */
+struct ez_httpresponse {
+       struct ez_http http;
+
+       char *version;
+       int code;
+       char *message;
+};
+
+/**
+ * HTTP connection, used for both client and server endpoints.
+ */
+struct ez_httpconnection {
+       struct ez_httpserver *server;
+
+       struct obstack os;
+       void *empty;
+
+       struct sockaddr_in addr;
+       int fd;
+       struct ez_blobio io;
+};
+
+/**
+ * Add headers to a request or response.
+ * Headers are appended.
+ */
+void http_addheaders(struct ez_http *h, struct ez_pair *list, int count);
+
+/**
+ * Set the payload for a request or response.
+ */
+void http_set_data(struct ez_http *h, const char *data, size_t size);
+
+/**
+ * Add url parameters for request.
+ */
+void httprequest_addparams(struct ez_httprequest *h, struct ez_pair *list, int count);
+
+/**
+ * Initialise a response for a given connection.
+ * See also http_addheaders(), http_set_data().
+ */
+//void httpresponse_init(struct ez_httpresponse *r, struct ez_httpconnection *c, int code, const char *msg);
+
+/**
+ * Set response code and message for status line.
+ */
+void httpresponse_set_response(struct ez_httpresponse *r, int code, const char *msg);
+
+/**
+ * Send a http response.
+ * The status line is constructed from the expanded fields (code, message).
+ */
+//int httpresponse_run(struct ez_httpresponse *r);
+
+/**
+ * HTTP server listening socket and state.
+ */
+struct ez_httpserver {
+       ez_set handlers;        // set of struct ez_httphandler
+
+       struct sockaddr_in addr;
+       int fd;
+};
+
+/**
+ * Handler for a given url path.
+ *
+ * The handler will receive a completed request and must fill out the response.
+ *
+ * A non-zero return indicates the server should terminate.
+ */
+struct ez_httphandler {
+       ez_node sn;
+
+       char *path;
+       int (*fn)(struct ez_httprequest *req, struct ez_httpresponse *rep);
+};
+
+int httpserver_init(struct ez_httpserver *s, int port);
+void httpserver_addhandlers(struct ez_httpserver *s, struct ez_httphandler *list, int count);
+int httpserver_run(struct ez_httpserver *s);
+void httpserver_free(struct ez_httpserver *s);
+
+/**
+ * HTTP Client
+ */
+struct ez_httpclient {
+       struct ez_httpconnection conn;
+       struct ez_httprequest request;
+       struct ez_httpresponse response;
+};
+
+/**
+ * Initialise HTTP client connection and request.
+ * Call http_addheaders to add additional headers.
+ * Call http_set_data to set payload.
+ */
+int httpclient_init(struct ez_httpclient *cc, const char *host, int port);
+
+/**
+ * Perform HTTP request.
+ * The response is available in ez_httpclient.response.
+ */
+int httpclient_run(struct ez_httpclient *cc, const char *method, const char *url);
+
+/**
+ * Free all httpclient resources.
+ */
+void httpclient_free(struct ez_httpclient *cc);
+
+#endif
index e605c74..74f12bb 100644 (file)
--- a/ez-node.h
+++ b/ez-node.h
@@ -126,4 +126,40 @@ static void *ez_node_link(void *node, enum ez_node_link_t i) __attribute__((alwa
 static void *ez_node_next(void *node) __attribute__((always_inline));
 static unsigned int ez_node_hash(void *node) __attribute__((always_inline));
 
+/**
+ * Extended node - a named node.
+ */
+typedef struct ez_string_node ez_string_node;
+
+struct ez_string_node {
+       struct ez_node ln;
+       char *name;
+};
+
+#include <string.h>
+
+// see also ez-set.h ez_string_node_hash()
+
+static __inline__ int ez_string_node_equals(void *a, void *b) {
+       return strcmp(((struct ez_string_node *)a)->name, ((struct ez_string_node *)b)->name) == 0;
+}
+
+static __inline__ int ez_string_node_cmp(void *a, void *b) {
+       return strcmp(((struct ez_string_node *)a)->name, ((struct ez_string_node *)b)->name);
+}
+
+/**
+ * Extended node - a name/value pair node.
+ *
+ * Can use the ez_string* functions for hashing and trees.
+ */
+typedef struct ez_pair ez_pair;
+
+struct ez_pair {
+       struct ez_node ln;
+       char *name;
+       char *value;
+};
+
+
 #endif
index beacf76..19ef7e2 100644 (file)
--- a/ez-set.h
+++ b/ez-set.h
@@ -237,4 +237,11 @@ static int ez_set_node_equals(ez_set *h, const void *a, const void *b) __attribu
 static void ez_set_node_free(ez_set *h, void *e) __attribute__((always_inline));
 static void *ez_set_scan_get(ez_set_scan *scan) __attribute__((always_inline));
 
+/**
+ * Extended node - string node for hash set.
+ */
+static __inline__ unsigned int ez_string_node_hash(void *n) {
+       return ez_hash_string(((struct ez_string_node *)n)->name);
+}
+
 #endif
index dbe51d3..6ecb3ce 100644 (file)
--- a/ez-tree.h
+++ b/ez-tree.h
@@ -232,5 +232,4 @@ static int ez_tree_empty(ez_tree *tree) __attribute__((always_inline));
 static int ez_tree_size(struct ez_tree *tree) __attribute__((always_inline));
 static void *ez_tree_root(struct ez_tree *tree) __attribute__((always_inline));
 
-
 #endif