aboutsummaryrefslogtreecommitdiff
path: root/src/supplemental/http/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/supplemental/http/server.c')
-rw-r--r--src/supplemental/http/server.c1544
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