diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-01-19 17:13:40 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-01-20 15:03:12 -0800 |
| commit | 7dbc9ae4fef34c6fd836b939bd559e54b8008b72 (patch) | |
| tree | 54660fef8a4a597c067a18953a295f1cfdb96cf0 /src/supplemental/http/server.c | |
| parent | 338706c2420ce3e51b546a6ba2574e10346a511b (diff) | |
| download | nng-7dbc9ae4fef34c6fd836b939bd559e54b8008b72.tar.gz nng-7dbc9ae4fef34c6fd836b939bd559e54b8008b72.tar.bz2 nng-7dbc9ae4fef34c6fd836b939bd559e54b8008b72.zip | |
fixes #216 HTTP server side API refactoring, directory serving support
This changes the backend (internal) HTTP API to provide a much more
sensible handler scheme, where the handlers are opaque objects and we
can allocate a handler for different types of tasks.
We've also added support serving up directories of static content, and
added code to validate that the directory serving is working as intended.
This is a key enabling step towards the public API.
Diffstat (limited to 'src/supplemental/http/server.c')
| -rw-r--r-- | src/supplemental/http/server.c | 877 |
1 files changed, 604 insertions, 273 deletions
diff --git a/src/supplemental/http/server.c b/src/supplemental/http/server.c index 700ec536..88a3bd1a 100644 --- a/src/supplemental/http/server.c +++ b/src/supplemental/http/server.c @@ -27,20 +27,20 @@ static nni_initializer http_server_initializer = { .i_once = 0, }; -typedef struct http_handler { +struct nni_http_handler { nni_list_node node; - void * h_arg; - char * h_path; - char * h_method; - char * h_host; - bool h_is_upgrader; - bool h_is_dir; - int h_refcnt; - void (*h_cb)(nni_aio *); - void (*h_free)(void *); -} http_handler; - -typedef struct http_sconn { + 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; @@ -72,9 +72,133 @@ struct nni_http_server { nni_url * url; }; +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_handler_fini(http_handler *); static void http_sconn_reap(void *arg) @@ -257,18 +381,23 @@ http_uri_canonify(char *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 /. + // 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. + // 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') { @@ -290,6 +419,8 @@ http_uri_canonify(char *path) tmp++; } *dst = '\0'; + + // XXX: this is where we should also remove /./ and /../ references. return (path); } @@ -312,29 +443,58 @@ http_sconn_error(http_sconn *sc, uint16_t err) 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; - http_handler * h; - const char * val; - nni_http_req * req = sc->req; - char * uri; - size_t urisz; - char * path; - bool badmeth = false; + 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. + // 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); @@ -347,22 +507,21 @@ http_sconn_rxdone(void *arg) } if (strcmp(val, "HTTP/1.1") != 0) { // We treat HTTP/1.0 connections as non-persistent. - // No effort is made to handle "persistent" HTTP/1.0 - // since that was not standard. (Everyone is at 1.1 now - // anyways.) + // 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 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 (7230) + // 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 that included the word close not - // as part of a whole token. No such legal definitions - // exist, and so anyone who does that gets what they - // deserve. (Fairly harmless actually, since it only + // 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; } @@ -377,71 +536,80 @@ http_sconn_rxdone(void *arg) 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->h_host != NULL) { - val = nni_http_req_get_header(req, "Host"); - if (val == NULL) { - // We insist on a matching Host: line for - // virtual hosting. This leaves HTTP/1.0 - // out in the cold basically. + if (h->host != NULL) { + if (host == NULL) { + // HTTP/1.0 cannot access virtual hosts. continue; } - // A few ways hosts can match. They might have - // a port attached -- we ignore that. (We don't - // run multiple ports, so if you got here, presumably - // the port at least is correct!) It might also have - // a lone trailing dot, so that is ok too. - - // Ignore the trailing dot if the handler supplied it. - len = strlen(h->h_host); - if ((len > 0) && (h->h_host[len - 1] == '.')) { - len--; - } - if ((nni_strncasecmp(val, h->h_host, len) != 0)) { + len = strlen(h->host); + if ((nni_strncasecmp(host, h->host, len) != 0)) { continue; } - if ((val[len] != '\0') && (val[len] != ':') && - ((val[len] != '.') || (val[len + 1] != '\0'))) { + + // 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; } } - NNI_ASSERT(h->h_method != NULL); - - len = strlen(h->h_path); - if (strncmp(path, h->h_path, len) != 0) { + 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->h_is_dir)) { - // trailing component and not a directory. - // Note that this should force a failure. + if ((path[len + 1] != '\0') && (!h->tree)) { + // Trailing component and not a directory. continue; } break; default: - continue; // some other substring, not matched. + 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->h_method) == 0) { + if (strcmp(val, h->method) == 0) { break; } - // HEAD is remapped to GET. + // HEAD is remapped to GET, but only if no HEAD specific + // handler registered. if ((strcmp(val, "HEAD") == 0) && - (strcmp(h->h_method, "GET") == 0)) { - break; + (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); @@ -454,30 +622,31 @@ http_sconn_rxdone(void *arg) return; } - nni_aio_set_input(sc->cbaio, 0, sc->http); - nni_aio_set_input(sc->cbaio, 1, sc->req); - nni_aio_set_input(sc->cbaio, 2, h->h_arg); + 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_aio_set_data(sc->cbaio, 1, h); - h->h_refcnt++; - h->h_cb(sc->cbaio); + 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; - http_handler * h; - nni_http_server *s = sc->server; - bool upgrader; + 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. @@ -489,25 +658,22 @@ http_sconn_cbdone(void *arg) res = nni_aio_get_output(aio, 0); nni_mtx_lock(&s->mtx); - upgrader = h->h_is_upgrader; - h->h_refcnt--; - if (h->h_refcnt == 0) { - http_handler_fini(h); + 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 (upgrader && (res == NULL)) { - sc->http = NULL; // the underlying HTTP is not closed - sc->req = NULL; - sc->res = NULL; - http_sconn_close(sc); // discard server session though + // 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)) { @@ -604,21 +770,9 @@ http_server_acccb(void *arg) } static void -http_handler_fini(http_handler *h) -{ - nni_strfree(h->h_path); - nni_strfree(h->h_host); - nni_strfree(h->h_method); - if (h->h_free != NULL) { - h->h_free(h->h_arg); - } - NNI_FREE_STRUCT(h); -} - -static void http_server_fini(nni_http_server *s) { - http_handler *h; + nni_http_handler *h; nni_aio_stop(s->accaio); @@ -631,7 +785,8 @@ http_server_fini(nni_http_server *s) } while ((h = nni_list_first(&s->handlers)) != NULL) { nni_list_remove(&s->handlers, h); - http_handler_fini(h); + h->refcnt--; + nni_http_handler_fini(h); } nni_mtx_unlock(&s->mtx); if (s->url != NULL) { @@ -691,7 +846,7 @@ http_server_init(nni_http_server **serverp, nni_url *url) s->url = url; nni_mtx_init(&s->mtx); nni_cv_init(&s->cv, &s->mtx); - NNI_LIST_INIT(&s->handlers, http_handler, node); + 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); @@ -708,9 +863,10 @@ http_server_init(nni_http_server **serverp, nni_url *url) } #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. + // 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); @@ -816,8 +972,8 @@ http_server_stop(nni_http_server *s) 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). + // 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); } @@ -836,50 +992,20 @@ nni_http_server_stop(nni_http_server *s) } int -http_server_add_handler(void **hp, nni_http_server *s, nni_http_handler *hh, - void *arg, void (*freeit)(void *)) +nni_http_server_add_handler(nni_http_server *s, nni_http_handler *h) { - http_handler *h, *h2; - size_t l1, l2; + 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 ((hh->h_method == NULL) || (hh->h_path == NULL) || - (hh->h_cb == NULL) || (strcmp(hh->h_method, "HEAD") == 0)) { + if (((len = strlen(h->path)) == 0) || (h->path[0] != '/') || + (h->cb == NULL)) { return (NNG_EINVAL); } - if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { - return (NNG_ENOMEM); - } - h->h_arg = arg; - h->h_cb = hh->h_cb; - h->h_is_dir = hh->h_is_dir; - h->h_is_upgrader = hh->h_is_upgrader; - h->h_free = freeit; - - // When registering the handler, only register *host*, not port. - if (hh->h_host != NULL) { - if ((h->h_host = nni_strdup(hh->h_host)) == NULL) { - http_handler_fini(h); - return (NNG_ENOMEM); - } - } - - if (((h->h_method = nni_strdup(hh->h_method)) == NULL) || - ((h->h_path = nni_strdup(hh->h_path)) == NULL)) { - http_handler_fini(h); - return (NNG_ENOMEM); - } - - l1 = strlen(h->h_path); - // Chop off trailing "/" - while (l1 > 0) { - if (h->h_path[l1 - 1] != '/') { - break; - } - l1--; - h->h_path[l1] = '\0'; + while ((len > 0) && (h->path[len - 1] == '/')) { + len--; // ignore trailing '/' (this collapses them) } nni_mtx_lock(&s->mtx); @@ -888,52 +1014,64 @@ http_server_add_handler(void **hp, nni_http_server *s, nni_http_handler *hh, // 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) { - if ((h2->h_host != NULL) && (h->h_host != NULL) && - (nni_strcasecmp(h2->h_host, h->h_host) != 0)) { + 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 (strcmp(h2->h_method, h->h_method) != 0) { + 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; } - l2 = strlen(h2->h_path); - if (l1 < l2) { - l2 = l1; + + 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 (strncmp(h2->h_path, h->h_path, l2) == 0) { - // Path collision. NNG_EADDRINUSE. + + 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); - http_handler_fini(h); return (NNG_EADDRINUSE); } } - h->h_refcnt = 1; + h->refcnt = 1; nni_list_append(&s->handlers, h); nni_mtx_unlock(&s->mtx); - if (hp != NULL) { - *hp = h; - } return (0); } -int -nni_http_server_add_handler( - void **hp, nni_http_server *s, nni_http_handler *hh, void *arg) -{ - return (http_server_add_handler(hp, s, hh, arg, NULL)); -} - void -nni_http_server_del_handler(nni_http_server *s, void *harg) +nni_http_server_del_handler(nni_http_server *s, nni_http_handler *h) { - http_handler *h = harg; - nni_mtx_lock(&s->mtx); - nni_list_node_remove(&h->node); - h->h_refcnt--; - if (h->h_refcnt == 0) { - http_handler_fini(h); + if (nni_list_node_active(&h->node)) { + nni_list_remove(&s->handlers, h); + h->refcnt--; } nni_mtx_unlock(&s->mtx); } @@ -996,21 +1134,23 @@ http_lookup_type(const char *path) return (NULL); } -typedef struct http_file { - char *typ; - char *pth; -} http_file; - static void http_handle_file(nni_aio *aio) { - http_file * f = nni_aio_get_input(aio, 2); - nni_http_res *res = NULL; - void * data; - size_t size; - int rv; - - if ((rv = nni_plat_file_get(f->pth, &data, &size)) != 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); + 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: @@ -1030,84 +1170,282 @@ http_handle_file(nni_aio *aio) nni_aio_finish_error(aio, rv); return; } - } else { - 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", f->typ)) != 0) || - ((rv = nni_http_res_set_data(res, data, size)) != 0)) { - nni_free(data, size); - 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_free_file(void *arg) +http_file_dtor(nni_http_handler *h) { - http_file *f = arg; - nni_strfree(f->pth); - nni_strfree(f->typ); - NNI_FREE_STRUCT(f); + char *p = nni_http_handler_get_data(h, 0); + nni_strfree(p); } int -nni_http_server_add_file(nni_http_server *s, const char *host, - const char *ctype, const char *uri, const char *path) +nni_http_handler_init_file_ctype(nni_http_handler **hpp, const char *uri, + const char *path, const char *ctype) { - nni_http_handler h; - http_file * f; - int rv; + nni_http_handler *h; + int rv; + char * p; - if ((f = NNI_ALLOC_STRUCT(f)) == NULL) { + // 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 = http_lookup_type(path); + 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; } - if (((f->pth = nni_strdup(path)) == NULL) || - ((ctype != NULL) && ((f->typ = nni_strdup(ctype)) == NULL))) { - http_free_file(f); + 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); } - h.h_method = "GET"; - h.h_path = uri; - h.h_host = host; - h.h_cb = http_handle_file; - h.h_is_dir = false; - h.h_is_upgrader = false; - if ((rv = http_server_add_handler(NULL, s, &h, f, http_free_file)) != + if ((rv = nni_http_handler_init(&h, uri, http_handle_directory)) != 0) { - http_free_file(f); + 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); } -typedef struct http_static { - char * typ; - void * data; - size_t size; -} http_static; - static void http_handle_static(nni_aio *aio) { - http_static * s = nni_aio_get_input(aio, 2); - nni_http_res *r = NULL; - int rv; + 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", s->typ)) != 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, s->data, s->size)) != 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; } @@ -1117,49 +1455,42 @@ http_handle_static(nni_aio *aio) } static void -http_free_static(void *arg) +http_static_dtor(nni_http_handler *h) { - http_static *s = arg; - nni_strfree(s->typ); - nni_free(s->data, s->size); - NNI_FREE_STRUCT(s); + 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_server_add_static(nni_http_server *s, const char *host, - const char *ctype, const char *uri, const void *data, size_t size) +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; - http_static * f; - int rv; + nni_http_handler *h; + int rv; + void * dat; - if ((f = NNI_ALLOC_STRUCT(f)) == NULL) { - return (NNG_ENOMEM); - } - if (ctype == NULL) { - ctype = "application/octet-stream"; - } - if (((f->data = nni_alloc(size)) == NULL) || - ((f->typ = nni_strdup(ctype)) == NULL)) { - http_free_static(f); + if ((dat = nni_alloc(size)) == NULL) { return (NNG_ENOMEM); } + memcpy(dat, data, size); - f->size = size; - memcpy(f->data, data, size); - - h.h_method = "GET"; - h.h_path = uri; - h.h_host = host; - h.h_cb = http_handle_static; - h.h_is_dir = false; - h.h_is_upgrader = false; + if ((rv = nni_http_handler_init(&h, uri, http_handle_static)) != 0) { + nni_free(dat, size); + return (rv); + } - if ((rv = http_server_add_handler(NULL, s, &h, f, http_free_static)) != - 0) { - http_free_static(f); + 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); } |
