diff options
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 |
