diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-01-24 17:38:16 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-02-01 16:11:38 -0800 |
| commit | 3dae30ed5e543dc73fc993334ef56b9b157b9b3c (patch) | |
| tree | d7e294b5d544aa18e8fc8749abfe605a05fa4bd7 /src/supplemental/http/server.c | |
| parent | 5914e40c2ff7fcf346c90705785f3fb7650a9fdc (diff) | |
| download | nng-3dae30ed5e543dc73fc993334ef56b9b157b9b3c.tar.gz nng-3dae30ed5e543dc73fc993334ef56b9b157b9b3c.tar.bz2 nng-3dae30ed5e543dc73fc993334ef56b9b157b9b3c.zip | |
fixes #173 Define public HTTP server API
This introduces enough of the HTTP API to support fully server
applications, including creation of websocket style protocols,
pluggable handlers, and so forth.
We have also introduced scatter/gather I/O (rudimentary) for
aios, and made other enhancements to the AIO framework. The
internals of the AIOs themselves are now fully private, and we
have eliminated the aio->a_addr member, with plans to remove the
pipe and possibly message members as well.
A few other minor issues were found and fixed as well.
The HTTP API includes request, response, and connection objects,
which can be used with both servers and clients. It also defines
the HTTP server and handler objects, which support server applications.
Support for client applications will require a client object to be
exposed, and that should be happening shortly.
None of this is "documented" yet, bug again, we will follow up shortly.
Diffstat (limited to 'src/supplemental/http/server.c')
| -rw-r--r-- | src/supplemental/http/server.c | 1544 |
1 files changed, 0 insertions, 1544 deletions
diff --git a/src/supplemental/http/server.c b/src/supplemental/http/server.c deleted file mode 100644 index ecb544ea..00000000 --- a/src/supplemental/http/server.c +++ /dev/null @@ -1,1544 +0,0 @@ -// -// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2018 Capitar IT Group BV <info@capitar.com> -// -// This software is supplied under the terms of the MIT License, a -// copy of which should be located in the distribution where this -// file was obtained (LICENSE.txt). A copy of the license may also be -// found online at https://opensource.org/licenses/MIT. -// - -#include <ctype.h> -#include <stdbool.h> -#include <stdio.h> -#include <string.h> - -#include "core/nng_impl.h" -#include "supplemental/tls/tls.h" - -#include "http.h" - -static int http_server_sys_init(void); -static void http_server_sys_fini(void); - -static nni_initializer http_server_initializer = { - .i_init = http_server_sys_init, - .i_fini = http_server_sys_fini, - .i_once = 0, -}; - -struct nni_http_handler { - nni_list_node node; - void ** args; - unsigned nargs; - char * path; - const char * method; - char * host; - bool tree; - int refcnt; - void (*cb)(nni_aio *); - void (*dtor)(nni_http_handler *); -}; - -typedef struct nni_http_ctx { - nni_list_node node; - nni_http * http; - nni_http_server *server; - nni_http_req * req; - nni_http_res * res; - bool close; - bool closed; - bool finished; - nni_aio * cbaio; - nni_aio * rxaio; - nni_aio * txaio; - nni_aio * txdataio; - nni_reap_item reap; -} http_sconn; - -struct nni_http_server { - nng_sockaddr addr; - nni_list_node node; - int refcnt; - int starts; - nni_list handlers; - nni_list conns; - nni_mtx mtx; - nni_cv cv; - bool closed; - nng_tls_config * tls; - nni_aio * accaio; - nni_plat_tcp_ep *tep; - char * port; - char * hostname; -}; - -int -nni_http_handler_init( - nni_http_handler **hp, const char *path, void (*cb)(nni_aio *)) -{ - nni_http_handler *h; - - if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { - return (NNG_ENOMEM); - } - if ((h->path = nni_strdup(path)) == NULL) { - NNI_FREE_STRUCT(h); - return (NNG_ENOMEM); - } - NNI_LIST_NODE_INIT(&h->node); - h->cb = cb; - h->args = NULL; - h->nargs = 0; - h->dtor = NULL; - h->method = "GET"; - h->host = NULL; - h->tree = false; - h->refcnt = 0; - *hp = h; - return (0); -} - -void -nni_http_handler_fini(nni_http_handler *h) -{ - if (h->refcnt != 0) { - return; - } - if (h->dtor != NULL) { - h->dtor(h); - } - if (h->nargs > 0) { - nni_free(h->args, h->nargs * sizeof(void *)); - } - nni_strfree(h->host); - nni_strfree(h->path); - NNI_FREE_STRUCT(h); -} - -int -nni_http_handler_set_data(nni_http_handler *h, void *data, unsigned index) -{ - if (h->refcnt != 0) { - return (NNG_EBUSY); - } - if (index >= h->nargs) { - void ** newargs; - unsigned newnargs = index + 4; // +4 to reduce allocations - if ((newargs = nni_alloc(newnargs * sizeof(void *))) == NULL) { - return (NNG_ENOMEM); - } - - memcpy(newargs, h->args, h->nargs * sizeof(void *)); - for (unsigned i = h->nargs; i < newnargs; i++) { - newargs[i] = NULL; - } - nni_free(h->args, h->nargs * sizeof(void *)); - h->args = newargs; - h->nargs = newnargs; - } - h->args[index] = data; - return (0); -} - -void * -nni_http_handler_get_data(nni_http_handler *h, unsigned index) -{ - return ((index < h->nargs) ? h->args[index] : NULL); -} - -int -nni_http_handler_set_tree(nni_http_handler *h, bool is_tree) -{ - if (h->refcnt != 0) { - return (NNG_EBUSY); - } - h->tree = is_tree; - return (0); -} - -int -nni_http_handler_set_host(nni_http_handler *h, const char *host) -{ - char *duphost; - if (h->refcnt != 0) { - return (NNG_EBUSY); - } - if (host == NULL) { - nni_strfree(h->host); - h->host = NULL; - return (0); - } - if ((duphost = nni_strdup(host)) == NULL) { - return (NNG_ENOMEM); - } - nni_strfree(h->host); - h->host = duphost; - return (0); -} - -int -nni_http_handler_set_method(nni_http_handler *h, const char *method) -{ - if (h->refcnt != 0) { - return (NNG_EBUSY); - } - h->method = method; - return (0); -} - -int -nni_http_handler_set_dtor( - nni_http_handler *h, void (*dtor)(nni_http_handler *)) -{ - if (h->refcnt != 0) { - return (NNG_EBUSY); - } - h->dtor = dtor; - return (0); -} - -static nni_list http_servers; -static nni_mtx http_servers_lk; - -static void -http_sconn_reap(void *arg) -{ - http_sconn * sc = arg; - nni_http_server *s = sc->server; - NNI_ASSERT(!sc->finished); - sc->finished = true; - nni_aio_stop(sc->rxaio); - nni_aio_stop(sc->txaio); - nni_aio_stop(sc->txdataio); - nni_aio_stop(sc->cbaio); - - if (sc->http != NULL) { - nni_http_fini(sc->http); - } - if (sc->req != NULL) { - nni_http_req_fini(sc->req); - } - if (sc->res != NULL) { - nni_http_res_fini(sc->res); - } - nni_aio_fini(sc->rxaio); - nni_aio_fini(sc->txaio); - nni_aio_fini(sc->txdataio); - nni_aio_fini(sc->cbaio); - - // Now it is safe to release our reference on the server. - nni_mtx_lock(&s->mtx); - if (nni_list_node_active(&sc->node)) { - nni_list_remove(&s->conns, sc); - if (nni_list_empty(&s->conns)) { - nni_cv_wake(&s->cv); - } - } - nni_mtx_unlock(&s->mtx); - - NNI_FREE_STRUCT(sc); -} - -static void -http_sconn_fini(http_sconn *sc) -{ - nni_reap(&sc->reap, http_sconn_reap, sc); -} - -static void -http_sconn_close_locked(http_sconn *sc) -{ - nni_http_server *s; - s = sc->server; - nni_http *h; - - if (sc->closed) { - return; - } - NNI_ASSERT(!sc->finished); - - sc->closed = true; - nni_aio_cancel(sc->rxaio, NNG_ECLOSED); - nni_aio_cancel(sc->txaio, NNG_ECLOSED); - nni_aio_cancel(sc->txdataio, NNG_ECLOSED); - nni_aio_cancel(sc->cbaio, NNG_ECLOSED); - - if ((h = sc->http) != NULL) { - nni_http_close(h); - } - http_sconn_fini(sc); -} - -static void -http_sconn_close(http_sconn *sc) -{ - nni_http_server *s; - s = sc->server; - - nni_mtx_lock(&s->mtx); - http_sconn_close_locked(sc); - nni_mtx_unlock(&s->mtx); -} - -static void -http_sconn_txdatdone(void *arg) -{ - http_sconn *sc = arg; - nni_aio * aio = sc->txdataio; - - if (nni_aio_result(aio) != 0) { - http_sconn_close(sc); - return; - } - - if (sc->res != NULL) { - nni_http_res_fini(sc->res); - sc->res = NULL; - } - - if (sc->close) { - http_sconn_close(sc); - return; - } - - nni_http_req_reset(sc->req); - nni_http_read_req(sc->http, sc->req, sc->rxaio); -} - -static void -http_sconn_txdone(void *arg) -{ - http_sconn *sc = arg; - nni_aio * aio = sc->txaio; - int rv; - void * data; - size_t size; - - if ((rv = nni_aio_result(aio)) != 0) { - http_sconn_close(sc); - return; - } - - // For HEAD requests, we just treat like "GET" but don't send - // the data. (Required per HTTP.) - if (strcmp(nni_http_req_get_method(sc->req), "HEAD") == 0) { - size = 0; - } else { - nni_http_res_get_data(sc->res, &data, &size); - } - if (size) { - // Submit data. - sc->txdataio->a_niov = 1; - sc->txdataio->a_iov[0].iov_buf = data; - sc->txdataio->a_iov[0].iov_len = size; - nni_http_write_full(sc->http, sc->txdataio); - return; - } - - if (sc->close) { - http_sconn_close(sc); - return; - } - - if (sc->res != NULL) { - nni_http_res_fini(sc->res); - sc->res = NULL; - } - nni_http_req_reset(sc->req); - nni_http_read_req(sc->http, sc->req, sc->rxaio); -} - -static char -http_hexval(char c) -{ - if ((c >= '0') && (c <= '9')) { - return (c - '0'); - } - if ((c >= 'a') && (c <= 'f')) { - return ((c - 'a') + 10); - } - if ((c >= 'A') && (c <= 'F')) { - return ((c - 'A') + 10); - } - return (0); -} - -static char * -http_uri_canonify(char *path) -{ - char *tmp; - char *dst; - - // Chomp off query string. - if ((tmp = strchr(path, '?')) != NULL) { - *tmp = '\0'; - } - // If the URI was absolute, make it relative. - if ((nni_strncasecmp(path, "http://", strlen("http://")) == 0) || - (nni_strncasecmp(path, "https://", strlen("https://")) == 0)) { - // Skip past the :// - path = strchr(path, ':'); - path += 3; - - // scan for the end of the host, distinguished by a / - // path delimiter. There might not be one, in which - // case the whole thing is the host and we assume the - // path is just /. - if ((path = strchr(path, '/')) == NULL) { - return ("/"); - } - } - - // Now we have to unescape things. Unescaping is a shrinking - // operation (strictly), so this is safe. This is just URL - // decode. Note that paths with an embedded NUL are going to be - // treated as though truncated. Don't be that guy that sends - // %00 in a URL. - // - // XXX: Normalizer needs to leave % encoded stuff in there if - // the characters to which they refer are reserved. See RFC 3986 - // section 6.2.2. - tmp = path; - dst = path; - while (*tmp != '\0') { - char c; - if ((c = *tmp) != '%') { - *dst++ = c; - tmp++; - continue; - } - if (isxdigit(tmp[1]) && isxdigit(tmp[2])) { - c = http_hexval(tmp[1]); - c *= 16; - c += http_hexval(tmp[2]); - *dst++ = c; - tmp += 3; - } - // garbage in, garbage out - *dst++ = c; - tmp++; - } - *dst = '\0'; - - // XXX: this is where we should also remove /./ and /../ references. - return (path); -} - -static void -http_sconn_error(http_sconn *sc, uint16_t err) -{ - nni_http_res *res; - - if (nni_http_res_init_error(&res, err) != 0) { - http_sconn_close(sc); - return; - } - - if (sc->close) { - if (nni_http_res_set_header(res, "Connection", "close") != 0) { - http_sconn_close(sc); - } - } - sc->res = res; - nni_http_write_res(sc->http, res, sc->txaio); -} - -int -nni_http_hijack(nni_http_ctx *ctx) -{ - nni_http_server *s = ctx->server; - - nni_mtx_lock(&s->mtx); - ctx->http = NULL; - ctx->req = NULL; - nni_mtx_unlock(&s->mtx); - return (0); -} - -int -nni_http_ctx_stream(nni_http_ctx *ctx, nni_http **hpp) -{ - nni_http_server *s = ctx->server; - - nni_mtx_lock(&s->mtx); - if ((*hpp = ctx->http) == NULL) { - nni_mtx_unlock(&s->mtx); - return (NNG_ECLOSED); - } - nni_mtx_unlock(&s->mtx); - return (0); -} - -static void -http_sconn_rxdone(void *arg) -{ - http_sconn * sc = arg; - nni_http_server * s = sc->server; - nni_aio * aio = sc->rxaio; - int rv; - nni_http_handler *h = NULL; - nni_http_handler *head = NULL; - const char * val; - nni_http_req * req = sc->req; - char * uri; - size_t urisz; - char * path; - bool badmeth = false; - bool needhost = false; - const char * host; - - if ((rv = nni_aio_result(aio)) != 0) { - http_sconn_close(sc); - return; - } - - // Validate the request -- it has to at least look like HTTP - // 1.x. We flatly refuse to deal with HTTP 0.9, and we can't - // cope with HTTP/2. - if ((val = nni_http_req_get_version(req)) == NULL) { - sc->close = true; - http_sconn_error(sc, NNI_HTTP_STATUS_BAD_REQUEST); - return; - } - if (strncmp(val, "HTTP/1.", 7) != 0) { - sc->close = true; - http_sconn_error(sc, NNI_HTTP_STATUS_HTTP_VERSION_NOT_SUPP); - return; - } - if (strcmp(val, "HTTP/1.1") != 0) { - // We treat HTTP/1.0 connections as non-persistent. - // No effort is made for non-standard "persistent" HTTP/1.0. - sc->close = true; - } else { - needhost = true; - } - - // If the connection was 1.0, or a connection: close was - // requested, then mark this close on our end. - if ((val = nni_http_req_get_header(req, "Connection")) != NULL) { - // HTTP 1.1 says these have to be case insensitive - if (nni_strcasestr(val, "close") != NULL) { - // In theory this could falsely match some other weird - // connection header with the substring close. No such - // values are defined, so anyone who does that gets - // what they deserve. (Harmless actually, since it only - // prevents persistent connections.) - sc->close = true; - } - } - - val = nni_http_req_get_uri(req); - urisz = strlen(val) + 1; - if ((uri = nni_alloc(urisz)) == NULL) { - http_sconn_close(sc); // out of memory - return; - } - strncpy(uri, val, urisz); - path = http_uri_canonify(uri); - - host = nni_http_req_get_header(req, "Host"); - if ((host == NULL) && (needhost)) { - // Per RFC 2616 14.23 we have to send 400 status here. - http_sconn_error(sc, NNI_HTTP_STATUS_BAD_REQUEST); - return; - } - - nni_mtx_lock(&s->mtx); - NNI_LIST_FOREACH (&s->handlers, h) { - size_t len; - if (h->host != NULL) { - if (host == NULL) { - // HTTP/1.0 cannot access virtual hosts. - continue; - } - - len = strlen(h->host); - if ((nni_strncasecmp(host, h->host, len) != 0)) { - continue; - } - - // At least the first part matches. If the ending - // part is a lone "." (legal in DNS), or a port - // number, we match it. (We do not validate the - // port number.) Note that there may be false matches - // with IPv6 addresses, but addresses shouldn't be - // used with virtual hosts anyway. With both addresses - // and ports, a false match would be unlikely since - // they'd still have to *connect* using that info. - if ((host[len] != '\0') && (host[len] != ':') && - ((host[len] != '.') || (host[len + 1] != '\0'))) { - continue; - } - } - - len = strlen(h->path); - if (strncmp(path, h->path, len) != 0) { - continue; - } - switch (path[len]) { - case '\0': - break; - case '/': - if ((path[len + 1] != '\0') && (!h->tree)) { - // Trailing component and not a directory. - continue; - } - break; - default: - continue; // Some other substring, not matched. - } - - if ((h->method == NULL) || (h->method[0] == '\0')) { - // Handler wants to process *all* methods. - break; - } - // So, what about the method? - val = nni_http_req_get_method(req); - if (strcmp(val, h->method) == 0) { - break; - } - // HEAD is remapped to GET, but only if no HEAD specific - // handler registered. - if ((strcmp(val, "HEAD") == 0) && - (strcmp(h->method, "GET") == 0)) { - head = h; - continue; - } - badmeth = 1; - } - - if ((h == NULL) && (head != NULL)) { - h = head; - } - nni_free(uri, urisz); - if (h == NULL) { - nni_mtx_unlock(&s->mtx); - if (badmeth) { - http_sconn_error( - sc, NNI_HTTP_STATUS_METHOD_NOT_ALLOWED); - } else { - http_sconn_error(sc, NNI_HTTP_STATUS_NOT_FOUND); - } - return; - } - - nni_aio_set_input(sc->cbaio, 0, sc->req); - nni_aio_set_input(sc->cbaio, 1, h); - nni_aio_set_input(sc->cbaio, 2, sc); - - // Technically, probably callback should initialize this with - // start, but we do it instead. - - if (nni_aio_start(sc->cbaio, NULL, NULL) != 0) { - nni_mtx_unlock(&s->mtx); - return; - } - nni_aio_set_data(sc->cbaio, 1, h); - h->refcnt++; - nni_mtx_unlock(&s->mtx); - h->cb(sc->cbaio); -} - -static void -http_sconn_cbdone(void *arg) -{ - http_sconn * sc = arg; - nni_aio * aio = sc->cbaio; - nni_http_res * res; - nni_http_handler *h; - nni_http_server * s = sc->server; - - if (nni_aio_result(aio) != 0) { - // Hard close, no further feedback. - http_sconn_close(sc); - return; - } - - h = nni_aio_get_data(aio, 1); - res = nni_aio_get_output(aio, 0); - - nni_mtx_lock(&s->mtx); - h->refcnt--; - if (h->refcnt == 0) { - nni_http_handler_fini(h); - } - nni_mtx_unlock(&s->mtx); - - // If its an upgrader, and they didn't give us back a response, - // it means that they took over, and we should just discard - // this session, without closing the underlying channel. - if (sc->http == NULL) { - // If this happens, then the session was hijacked. - // We close the context, but the http channel stays up. - http_sconn_close(sc); - return; - } - if (res != NULL) { - const char *val; - val = nni_http_res_get_header(res, "Connection"); - if ((val != NULL) && (strstr(val, "close") != NULL)) { - sc->close = true; - } - if (sc->close) { - nni_http_res_set_header(res, "Connection", "close"); - } - sc->res = res; - nni_http_write_res(sc->http, res, sc->txaio); - } else if (sc->close) { - http_sconn_close(sc); - } else { - // Presumably client already sent a response. - // Wait for another request. - nni_http_req_reset(sc->req); - nni_http_read_req(sc->http, sc->req, sc->rxaio); - } -} - -static int -http_sconn_init(http_sconn **scp, nni_http_server *s, nni_plat_tcp_pipe *tcp) -{ - http_sconn *sc; - int rv; - - if ((sc = NNI_ALLOC_STRUCT(sc)) == NULL) { - nni_plat_tcp_pipe_fini(tcp); - return (NNG_ENOMEM); - } - - if (((rv = nni_http_req_init(&sc->req)) != 0) || - ((rv = nni_aio_init(&sc->rxaio, http_sconn_rxdone, sc)) != 0) || - ((rv = nni_aio_init(&sc->txaio, http_sconn_txdone, sc)) != 0) || - ((rv = nni_aio_init(&sc->txdataio, http_sconn_txdatdone, sc)) != - 0) || - ((rv = nni_aio_init(&sc->cbaio, http_sconn_cbdone, sc)) != 0)) { - // Can't even accept the incoming request. Hard close. - http_sconn_close(sc); - return (rv); - } - - if (s->tls != NULL) { - rv = nni_http_init_tls(&sc->http, s->tls, tcp); - } else { - rv = nni_http_init_tcp(&sc->http, tcp); - } - if (rv != 0) { - http_sconn_close(sc); - return (rv); - } - *scp = sc; - return (0); -} - -static void -http_server_acccb(void *arg) -{ - nni_http_server * s = arg; - nni_aio * aio = s->accaio; - nni_plat_tcp_pipe *tcp; - http_sconn * sc; - int rv; - - nni_mtx_lock(&s->mtx); - if ((rv = nni_aio_result(aio)) != 0) { - if (!s->closed) { - // try again? - nni_plat_tcp_ep_accept(s->tep, s->accaio); - } - nni_mtx_unlock(&s->mtx); - return; - } - tcp = nni_aio_get_pipe(aio); - if (s->closed) { - // If we're closing, then reject this one. - nni_plat_tcp_pipe_fini(tcp); - nni_mtx_unlock(&s->mtx); - return; - } - if (http_sconn_init(&sc, s, tcp) != 0) { - // The TCP structure is already cleaned up. - // Start another accept attempt. - nni_plat_tcp_ep_accept(s->tep, s->accaio); - nni_mtx_unlock(&s->mtx); - return; - } - sc->server = s; - nni_list_append(&s->conns, sc); - - nni_http_read_req(sc->http, sc->req, sc->rxaio); - nni_plat_tcp_ep_accept(s->tep, s->accaio); - nni_mtx_unlock(&s->mtx); -} - -static void -http_server_fini(nni_http_server *s) -{ - nni_http_handler *h; - - nni_aio_stop(s->accaio); - - nni_mtx_lock(&s->mtx); - while (!nni_list_empty(&s->conns)) { - nni_cv_wait(&s->cv); - } - if (s->tep != NULL) { - nni_plat_tcp_ep_fini(s->tep); - } - while ((h = nni_list_first(&s->handlers)) != NULL) { - nni_list_remove(&s->handlers, h); - h->refcnt--; - nni_http_handler_fini(h); - } - nni_mtx_unlock(&s->mtx); -#ifdef NNG_SUPP_TLS - if (s->tls != NULL) { - nni_tls_config_fini(s->tls); - } -#endif - nni_aio_fini(s->accaio); - nni_cv_fini(&s->cv); - nni_mtx_fini(&s->mtx); - nni_strfree(s->hostname); - nni_strfree(s->port); - NNI_FREE_STRUCT(s); -} - -void -nni_http_server_fini(nni_http_server *s) -{ - nni_mtx_lock(&http_servers_lk); - s->refcnt--; - if (s->refcnt == 0) { - nni_list_remove(&http_servers, s); - http_server_fini(s); - } - nni_mtx_unlock(&http_servers_lk); -} - -static int -http_server_init(nni_http_server **serverp, nni_url *url) -{ - nni_http_server *s; - int rv; - const char * port; - nni_aio * aio; - - port = url->u_port; - if ((strcmp(url->u_scheme, "http") != 0) && -#ifdef NNG_SUPP_TLS - (strcmp(url->u_scheme, "https") != 0) && - (strcmp(url->u_scheme, "wss") != 0) && -#endif - (strcmp(url->u_scheme, "ws") != 0)) { - return (NNG_EADDRINVAL); - } - if ((s = NNI_ALLOC_STRUCT(s)) == NULL) { - return (NNG_ENOMEM); - } - nni_mtx_init(&s->mtx); - nni_cv_init(&s->cv, &s->mtx); - NNI_LIST_INIT(&s->handlers, nni_http_handler, node); - NNI_LIST_INIT(&s->conns, http_sconn, node); - if ((rv = nni_aio_init(&s->accaio, http_server_acccb, s)) != 0) { - http_server_fini(s); - return (rv); - } - - if ((strlen(url->u_port)) && - ((s->port = nni_strdup(url->u_port)) == NULL)) { - http_server_fini(s); - return (NNG_ENOMEM); - } - if ((strlen(url->u_hostname)) && - ((s->hostname = nni_strdup(url->u_hostname)) == NULL)) { - http_server_fini(s); - return (NNG_ENOMEM); - } - -#ifdef NNG_SUPP_TLS - if ((strcmp(url->u_scheme, "https") == 0) || - (strcmp(url->u_scheme, "wss") == 0)) { - rv = nni_tls_config_init(&s->tls, NNG_TLS_MODE_SERVER); - if (rv != 0) { - http_server_fini(s); - return (rv); - } - } -#endif - - // Do the DNS lookup *now*. This means that this is - // synchronous, but it should be fast, since it should either - // resolve as a number, or resolve locally, without having to - // hit up DNS. - if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { - http_server_fini(s); - return (rv); - } - aio->a_addr = &s->addr; - nni_plat_tcp_resolv(s->hostname, s->port, NNG_AF_UNSPEC, true, aio); - nni_aio_wait(aio); - rv = nni_aio_result(aio); - nni_aio_fini(aio); - if (rv != 0) { - http_server_fini(s); - return (rv); - } - s->refcnt = 1; - *serverp = s; - return (0); -} - -int -nni_http_server_init(nni_http_server **serverp, nni_url *url) -{ - int rv; - nni_http_server *s; - - nni_initialize(&http_server_initializer); - - nni_mtx_lock(&http_servers_lk); - NNI_LIST_FOREACH (&http_servers, s) { - if ((strcmp(url->u_port, s->port) == 0) && - (strcmp(url->u_hostname, s->hostname) == 0)) { - *serverp = s; - s->refcnt++; - nni_mtx_unlock(&http_servers_lk); - return (0); - } - } - - // We didn't find a server, try to make a new one. - if ((rv = http_server_init(&s, url)) == 0) { - nni_list_append(&http_servers, s); - *serverp = s; - } - - nni_mtx_unlock(&http_servers_lk); - return (rv); -} - -static int -http_server_start(nni_http_server *s) -{ - int rv; - rv = nni_plat_tcp_ep_init(&s->tep, &s->addr, NULL, NNI_EP_MODE_LISTEN); - if (rv != 0) { - return (rv); - } - if ((rv = nni_plat_tcp_ep_listen(s->tep)) != 0) { - nni_plat_tcp_ep_fini(s->tep); - s->tep = NULL; - return (rv); - } - nni_plat_tcp_ep_accept(s->tep, s->accaio); - return (0); -} - -int -nni_http_server_start(nni_http_server *s) -{ - int rv = 0; - - nni_mtx_lock(&s->mtx); - if (s->starts == 0) { - rv = http_server_start(s); - } - if (rv == 0) { - s->starts++; - } - nni_mtx_unlock(&s->mtx); - return (rv); -} - -static void -http_server_stop(nni_http_server *s) -{ - http_sconn *sc; - - if (s->closed) { - return; - } - - s->closed = true; - // Close the TCP endpoint that is listening. - if (s->tep) { - nni_plat_tcp_ep_close(s->tep); - } - - // Stopping the server is a hard stop -- it aborts any work - // being done by clients. (No graceful shutdown). - NNI_LIST_FOREACH (&s->conns, sc) { - http_sconn_close_locked(sc); - } - nni_cv_wake(&s->cv); -} - -void -nni_http_server_stop(nni_http_server *s) -{ - nni_mtx_lock(&s->mtx); - s->starts--; - if (s->starts == 0) { - http_server_stop(s); - } - nni_mtx_unlock(&s->mtx); -} - -int -nni_http_server_add_handler(nni_http_server *s, nni_http_handler *h) -{ - nni_http_handler *h2; - size_t len; - - // Must have a legal method (and not one that is HEAD), path, - // and handler. (The reason HEAD is verboten is that we supply - // it automatically as part of GET support.) - if (((len = strlen(h->path)) == 0) || (h->path[0] != '/') || - (h->cb == NULL)) { - return (NNG_EINVAL); - } - while ((len > 0) && (h->path[len - 1] == '/')) { - len--; // ignore trailing '/' (this collapses them) - } - - nni_mtx_lock(&s->mtx); - // General rule for finding a conflict is that if either string - // is a strict substring of the other, then we have a - // collision. (But only if the methods match, and the host - // matches.) Note that a wild card host matches both. - NNI_LIST_FOREACH (&s->handlers, h2) { - size_t len2; - - if ((h2->host != NULL) && (h->host != NULL) && - (nni_strcasecmp(h2->host, h->host) != 0)) { - // Hosts don't match, so we are safe. - continue; - } - if (((h2->host == NULL) && (h->host != NULL)) || - ((h->host == NULL) && (h2->host != NULL))) { - continue; // Host specified for just one. - } - if (((h->method == NULL) && (h2->method != NULL)) || - ((h2->method == NULL) && (h->method != NULL))) { - continue; // Method specified for just one. - } - if ((h->method != NULL) && - (strcmp(h2->method, h->method) != 0)) { - // Different methods, so again we are fine. - continue; - } - - len2 = strlen(h2->path); - - while ((len2 > 0) && (h2->path[len2 - 1] == '/')) { - len2--; // ignore trailing '/' - } - if (strncmp(h->path, h2->path, len > len2 ? len2 : len) != 0) { - continue; // prefixes don't match. - } - - if (len2 > len) { - if ((h2->path[len] == '/') && (h->tree)) { - nni_mtx_unlock(&s->mtx); - return (NNG_EADDRINUSE); - } - } else if (len > len2) { - if ((h->path[len2] == '/') && (h2->tree)) { - nni_mtx_unlock(&s->mtx); - return (NNG_EADDRINUSE); - } - } else { - nni_mtx_unlock(&s->mtx); - return (NNG_EADDRINUSE); - } - } - h->refcnt = 1; - nni_list_append(&s->handlers, h); - nni_mtx_unlock(&s->mtx); - return (0); -} - -void -nni_http_server_del_handler(nni_http_server *s, nni_http_handler *h) -{ - nni_mtx_lock(&s->mtx); - if (nni_list_node_active(&h->node)) { - nni_list_remove(&s->handlers, h); - h->refcnt--; - } - nni_mtx_unlock(&s->mtx); -} - -// Very limited MIME type map. Used only if the handler does not -// supply it's own. -static struct content_map { - const char *ext; - const char *typ; -} content_map[] = { - // clang-format off - { ".ai", "application/postscript" }, - { ".aif", "audio/aiff" }, - { ".aiff", "audio/aiff" }, - { ".avi", "video/avi" }, - { ".au", "audio/basic" }, - { ".bin", "application/octet-stream" }, - { ".bmp", "image/bmp" }, - { ".css", "text/css" }, - { ".eps", "application/postscript" }, - { ".gif", "image/gif" }, - { ".htm", "text/html" }, - { ".html", "text/html" }, - { ".ico", "image/x-icon" }, - { ".jpeg", "image/jpeg" }, - { ".jpg", "image/jpeg" }, - { ".js", "application/javascript" }, - { ".md", "text/markdown" }, - { ".mp2", "video/mpeg" }, - { ".mp3", "audio/mpeg3" }, - { ".mpeg", "video/mpeg" }, - { ".mpg", "video/mpeg" }, - { ".pdf", "application/pdf" }, - { ".png", "image/png" }, - { ".ps", "application/postscript" }, - { ".rtf", "text/rtf" }, - { ".text", "text/plain" }, - { ".tif", "image/tiff" }, - { ".tiff", "image/tiff" }, - { ".txt", "text/plain" }, - { ".wav", "audio/wav"}, - { "README", "text/plain" }, - { NULL, NULL }, - // clang-format on -}; - -const char * -http_lookup_type(const char *path) -{ - size_t l1 = strlen(path); - for (int i = 0; content_map[i].ext != NULL; i++) { - size_t l2 = strlen(content_map[i].ext); - if (l2 > l1) { - continue; - } - if (nni_strcasecmp(&path[l1 - l2], content_map[i].ext) == 0) { - return (content_map[i].typ); - } - } - return (NULL); -} - -static void -http_handle_file(nni_aio *aio) -{ - nni_http_handler *h = nni_aio_get_input(aio, 1); - nni_http_res * res = NULL; - void * data; - size_t size; - int rv; - char * path = nni_http_handler_get_data(h, 0); - char * ctype = nni_http_handler_get_data(h, 1); - - // This is a very simplistic file server, suitable only for small - // files. In the future we can use an AIO based file read, where - // we read files a bit at a time, or even mmap them, and serve - // them up chunkwise. Applications could even come up with their own - // caching version of the http handler. - if ((rv = nni_file_get(path, &data, &size)) != 0) { - uint16_t status; - switch (rv) { - case NNG_ENOMEM: - status = NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR; - break; - case NNG_ENOENT: - status = NNI_HTTP_STATUS_NOT_FOUND; - break; - case NNG_EPERM: - status = NNI_HTTP_STATUS_FORBIDDEN; - break; - default: - status = NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR; - break; - } - if ((rv = nni_http_res_init_error(&res, status)) != 0) { - nni_aio_finish_error(aio, rv); - return; - } - nni_aio_set_output(aio, 0, res); - nni_aio_finish(aio, 0, 0); - return; - } - if (((rv = nni_http_res_init(&res)) != 0) || - ((rv = nni_http_res_set_status(res, NNI_HTTP_STATUS_OK, "OK")) != - 0) || - ((rv = nni_http_res_set_header(res, "Content-Type", ctype)) != - 0) || - ((rv = nni_http_res_copy_data(res, data, size)) != 0)) { - if (res != NULL) { - nni_http_res_fini(res); - } - nni_free(data, size); - nni_aio_finish_error(aio, rv); - return; - } - - nni_free(data, size); - nni_aio_set_output(aio, 0, res); - nni_aio_finish(aio, 0, 0); -} - -static void -http_file_dtor(nni_http_handler *h) -{ - char *p = nni_http_handler_get_data(h, 0); - nni_strfree(p); -} - -int -nni_http_handler_init_file_ctype(nni_http_handler **hpp, const char *uri, - const char *path, const char *ctype) -{ - nni_http_handler *h; - int rv; - char * p; - - // Later we might want to do this in the server side, if we support - // custom media type lists on a per-server basis. For now doing this - // here ensures that we don't have to lookup the type every time. - if (ctype == NULL) { - if ((ctype = http_lookup_type(path)) == NULL) { - ctype = "application/octet-stream"; - } - } - if ((p = nni_strdup(path)) == NULL) { - return (NNG_ENOMEM); - } - - if ((rv = nni_http_handler_init(&h, uri, http_handle_file)) != 0) { - nni_strfree(p); - return (rv); - } - - if (((rv = nni_http_handler_set_data(h, p, 0)) != 0) || - ((rv = nni_http_handler_set_data(h, (void *) ctype, 1)) != 0) || - ((rv = nni_http_handler_set_dtor(h, http_file_dtor)))) { - nni_strfree(p); - nni_http_handler_fini(h); - return (rv); - } - - *hpp = h; - return (0); -} - -int -nni_http_handler_init_file( - nni_http_handler **hpp, const char *uri, const char *path) -{ - return (nni_http_handler_init_file_ctype(hpp, uri, path, NULL)); -} - -static void -http_directory_dtor(nni_http_handler *h) -{ - char *path = nni_http_handler_get_data(h, 0); - char *base = nni_http_handler_get_data(h, 1); - nni_strfree(path); - nni_strfree(base); -} - -static void -http_handle_directory(nni_aio *aio) -{ - nni_http_req * req = nni_aio_get_input(aio, 0); - nni_http_handler *h = nni_aio_get_input(aio, 1); - nni_http_res * res = NULL; - void * data; - size_t size; - int rv; - char * path = nni_http_handler_get_data(h, 0); - const char * base = nni_http_handler_get_data(h, 1); // base uri - const char * uri = nni_http_req_get_uri(req); - const char * ctype; - char * dst; - size_t len; - size_t pnsz; - char * pn; - - len = strlen(base); - if ((strncmp(uri, base, len) != 0) || - ((uri[len] != 0) && (uri[len] != '/'))) { - // This should never happen! - nni_aio_finish_error(aio, NNG_EINVAL); - return; - } - - // simple worst case is every character in path is a seperator - // It's never actually that bad, because we we have /<something>/. - pnsz = (strlen(path) + strlen(uri) + 2) * strlen(NNG_PLATFORM_DIR_SEP); - pnsz += strlen("index.html") + 1; // +1 for term nul - - if ((pn = nni_alloc(pnsz)) == NULL) { - nni_aio_finish_error(aio, NNG_ENOMEM); - return; - } - - // make sure we have a "/" present. - strcpy(pn, path); - dst = pn + strlen(pn); - - if ((dst == pn) || (dst[-1] != '/')) { - *dst++ = '/'; - } - - for (uri = uri + len; *uri != '\0'; uri++) { - if (*uri == '/') { - strcpy(dst, NNG_PLATFORM_DIR_SEP); - dst += sizeof(NNG_PLATFORM_DIR_SEP) - 1; - } else { - *dst++ = *uri; - } - } - - *dst = '\0'; - - // This is a very simplistic file server, suitable only for small - // files. In the future we can use an AIO based file read, where - // we read files a bit at a time, or even mmap them, and serve - // them up chunkwise. Applications could even come up with their - // own caching version of the http handler. - - rv = 0; - if (nni_file_is_dir(pn)) { - sprintf(dst, "%s%s", NNG_PLATFORM_DIR_SEP, "index.html"); - if (!nni_file_is_file(pn)) { - pn[strlen(pn) - 1] = '\0'; // index.html -> index.htm - } - if (!nni_file_is_file(pn)) { - rv = NNG_ENOENT; - } - } - - if (rv == 0) { - rv = nni_file_get(pn, &data, &size); - } else { - data = NULL; - size = 0; - } - ctype = http_lookup_type(pn); - if (ctype == NULL) { - ctype = "application/octet-stream"; - } - - nni_free(pn, pnsz); - if (rv != 0) { - uint16_t status; - - switch (rv) { - case NNG_ENOMEM: - status = NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR; - break; - case NNG_ENOENT: - status = NNI_HTTP_STATUS_NOT_FOUND; - break; - case NNG_EPERM: - status = NNI_HTTP_STATUS_FORBIDDEN; - break; - default: - status = NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR; - break; - } - if ((rv = nni_http_res_init_error(&res, status)) != 0) { - nni_aio_finish_error(aio, rv); - return; - } - nni_aio_set_output(aio, 0, res); - nni_aio_finish(aio, 0, 0); - return; - } - - if (((rv = nni_http_res_init(&res)) != 0) || - ((rv = nni_http_res_set_status(res, NNI_HTTP_STATUS_OK, "OK")) != - 0) || - ((rv = nni_http_res_set_header(res, "Content-Type", ctype)) != - 0) || - ((rv = nni_http_res_copy_data(res, data, size)) != 0)) { - if (res != NULL) { - nni_http_res_fini(res); - } - nni_free(data, size); - nni_aio_finish_error(aio, rv); - return; - } - - nni_free(data, size); - nni_aio_set_output(aio, 0, res); - nni_aio_finish(aio, 0, 0); -} - -int -nni_http_handler_init_directory( - nni_http_handler **hpp, const char *uri, const char *path) -{ - nni_http_handler *h; - int rv; - char * p; - char * b; - - if ((p = nni_strdup(path)) == NULL) { - return (NNG_ENOMEM); - } - if ((b = nni_strdup(uri)) == NULL) { - nni_strfree(p); - return (NNG_ENOMEM); - } - - if ((rv = nni_http_handler_init(&h, uri, http_handle_directory)) != - 0) { - nni_strfree(p); - return (rv); - } - - if (((rv = nni_http_handler_set_data(h, p, 0)) != 0) || - ((rv = nni_http_handler_set_data(h, b, 1)) != 0) || - ((rv = nni_http_handler_set_tree(h, true)) != 0) || - ((rv = nni_http_handler_set_dtor(h, http_directory_dtor)))) { - nni_strfree(p); - nni_strfree(b); - nni_http_handler_fini(h); - return (rv); - } - - *hpp = h; - return (0); -} - -static void -http_handle_static(nni_aio *aio) -{ - void * data; - size_t size; - char * ctype; - nni_http_handler *h; - nni_http_res * r = NULL; - int rv; - - h = nni_aio_get_input(aio, 1); - data = nni_http_handler_get_data(h, 0); - size = (size_t)(uintptr_t) nni_http_handler_get_data(h, 1); - ctype = nni_http_handler_get_data(h, 2); - - if (ctype == NULL) { - ctype = "application/octet-stream"; - } - - if (((rv = nni_http_res_init(&r)) != 0) || - ((rv = nni_http_res_set_header(r, "Content-Type", ctype)) != 0) || - ((rv = nni_http_res_set_status(r, NNI_HTTP_STATUS_OK, "OK")) != - 0) || - ((rv = nni_http_res_set_data(r, data, size)) != 0)) { - if (r != NULL) { - nni_http_res_fini(r); - } - nni_aio_finish_error(aio, rv); - return; - } - - nni_aio_set_output(aio, 0, r); - nni_aio_finish(aio, 0, 0); -} - -static void -http_static_dtor(nni_http_handler *h) -{ - void * data = nni_http_handler_get_data(h, 0); - size_t size = (size_t)(uintptr_t) nni_http_handler_get_data(h, 1); - - nni_free(data, size); -} - -int -nni_http_handler_init_static(nni_http_handler **hpp, const char *uri, - const void *data, size_t size, const char *ctype) -{ - nni_http_handler *h; - int rv; - void * dat; - - if ((dat = nni_alloc(size)) == NULL) { - return (NNG_ENOMEM); - } - memcpy(dat, data, size); - - if ((rv = nni_http_handler_init(&h, uri, http_handle_static)) != 0) { - nni_free(dat, size); - return (rv); - } - - if (((rv = nni_http_handler_set_data(h, dat, 0)) != 0) || - ((rv = nni_http_handler_set_data(h, (void *) size, 1)) != 0) || - ((rv = nni_http_handler_set_data(h, (void *) ctype, 2)) != 0) || - ((rv = nni_http_handler_set_dtor(h, http_static_dtor)) != 0)) { - nni_free(dat, size); - nni_http_handler_fini(h); - return (rv); - } - - *hpp = h; - return (0); -} - -int -nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tcfg) -{ -#ifdef NNG_SUPP_TLS - nng_tls_config *old; - nni_mtx_lock(&s->mtx); - if (s->starts) { - nni_mtx_unlock(&s->mtx); - return (NNG_EBUSY); - } - old = s->tls; - s->tls = tcfg; - if (tcfg) { - nni_tls_config_hold(tcfg); - } - nni_mtx_unlock(&s->mtx); - if (old) { - nni_tls_config_fini(old); - } - return (0); -#else - return (NNG_ENOTSUP); -#endif -} - -int -nni_http_server_get_tls(nni_http_server *s, nng_tls_config **tp) -{ -#ifdef NNG_SUPP_TLS - nni_mtx_lock(&s->mtx); - if (s->tls == NULL) { - nni_mtx_unlock(&s->mtx); - return (NNG_EINVAL); - } - *tp = s->tls; - nni_mtx_unlock(&s->mtx); - return (0); -#else - return (NNG_ENOTSUP); -#endif -} - -static int -http_server_sys_init(void) -{ - NNI_LIST_INIT(&http_servers, nni_http_server, node); - nni_mtx_init(&http_servers_lk); - return (0); -} - -static void -http_server_sys_fini(void) -{ - nni_mtx_fini(&http_servers_lk); -}
\ No newline at end of file |
