From: Not Zed Date: Mon, 4 Jan 2021 02:37:30 +0000 (+1030) Subject: Added minimal embedded http client and server. X-Git-Url: https://code.zedzone.au/cvs?a=commitdiff_plain;h=c89d0738b54e21e424fb77ab01842e4008d93d92;p=libeze Added minimal embedded http client and server. Minor tweaks to blob compiler. Added some specific node types to ez-node. --- diff --git a/.gitignore b/.gitignore index 1905831..d8f1701 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .deps libeze.a +*.o + diff --git a/Makefile b/Makefile index 115646f..94348f5 100644 --- 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 \ diff --git a/ez-blob-compiler.c b/ez-blob-compiler.c index 2050d3f..cb9a41f 100644 --- a/ez-blob-compiler.c +++ b/ez-blob-compiler.c @@ -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 \n"); - printf("#include \n"); + printf("#include \n"); printf("#include \n"); printf("#include \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); diff --git a/ez-blob-io.h b/ez-blob-io.h index 8e07197..1c01620 100644 --- a/ez-blob-io.h +++ b/ez-blob-io.h @@ -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 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 + . +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#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" + +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;ihandlers, &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;iparams, &list[i]); +} + +void http_addheaders(struct ez_http *http, struct ez_pair *list, int count) { + for (int i=0;iheaders, &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 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 + . +*/ + +#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 diff --git a/ez-node.h b/ez-node.h index e605c74..74f12bb 100644 --- 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 + +// 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 diff --git a/ez-set.h b/ez-set.h index beacf76..19ef7e2 100644 --- 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 diff --git a/ez-tree.h b/ez-tree.h index dbe51d3..6ecb3ce 100644 --- 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