diff options
| -rw-r--r-- | src/core/file.c | 20 | ||||
| -rw-r--r-- | src/core/file.h | 8 | ||||
| -rw-r--r-- | src/platform/posix/posix_impl.h | 5 | ||||
| -rw-r--r-- | src/platform/windows/win_impl.h | 6 | ||||
| -rw-r--r-- | src/supplemental/http/http.h | 204 | ||||
| -rw-r--r-- | src/supplemental/http/server.c | 877 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.c | 68 | ||||
| -rw-r--r-- | tests/httpserver.c | 260 |
8 files changed, 1034 insertions, 414 deletions
diff --git a/src/core/file.c b/src/core/file.c index d7000a6a..4715be2a 100644 --- a/src/core/file.c +++ b/src/core/file.c @@ -28,6 +28,26 @@ nni_file_delete(const char *name) return (nni_plat_file_delete(name)); } +bool +nni_file_is_file(const char *name) +{ + int ft; + if ((nni_file_type(name, &ft) == 0) && (ft == NNI_FILE_TYPE_FILE)) { + return (true); + } + return (false); +} + +bool +nni_file_is_dir(const char *name) +{ + int ft; + if ((nni_file_type(name, &ft) == 0) && (ft == NNI_FILE_TYPE_DIR)) { + return (true); + } + return (false); +} + struct walkdata { nni_file_walker fn; void * arg; diff --git a/src/core/file.h b/src/core/file.h index b45aae61..7969ae5f 100644 --- a/src/core/file.h +++ b/src/core/file.h @@ -76,4 +76,12 @@ extern char *nni_file_join(const char *, const char *); // The returned value generally is within the supplied path name. extern const char *nni_file_basename(const char *); +// nni_file_is_file returns true if the path references a file. It returns +// false if an error occurs, or the path references something else. +extern bool nni_file_is_file(const char *); + +// nni_file_is_dir returns true if the path references a directroy. It returns +// false if an error occurs, or the path references something else. +extern bool nni_file_is_dir(const char *); + #endif // CORE_FILE_H diff --git a/src/platform/posix/posix_impl.h b/src/platform/posix/posix_impl.h index 0f238e45..bf79a449 100644 --- a/src/platform/posix/posix_impl.h +++ b/src/platform/posix/posix_impl.h @@ -1,5 +1,6 @@ // -// Copyright 2017 Garrett D'Amore <garrett@damore.org> +// 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 @@ -75,6 +76,8 @@ struct nni_plat_thr { void *arg; }; +#define NNG_PLATFORM_DIR_SEP "/" + #endif extern int nni_posix_pollq_sysinit(void); diff --git a/src/platform/windows/win_impl.h b/src/platform/windows/win_impl.h index 236feb31..0c22c240 100644 --- a/src/platform/windows/win_impl.h +++ b/src/platform/windows/win_impl.h @@ -1,6 +1,6 @@ // -// Copyright 2017 Garrett D'Amore <garrett@damore.org> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// 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 @@ -104,6 +104,8 @@ extern void nni_win_resolv_sysfini(void); extern int nni_win_sockaddr2nn(nni_sockaddr *, const SOCKADDR_STORAGE *); extern int nni_win_nn2sockaddr(SOCKADDR_STORAGE *, const nni_sockaddr *); +#define NNG_PLATFORM_DIR_SEP "\\" + #endif // NNG_PLATFORM_WINDOWS #endif // PLATFORM_WIN_IMPL_H diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h index 4e2ac697..08156714 100644 --- a/src/supplemental/http/http.h +++ b/src/supplemental/http/http.h @@ -163,70 +163,8 @@ extern int nni_http_peer_addr(nni_http *, nni_sockaddr *); // nni_tls_http_verified returns true if the peer has been verified using TLS. extern bool nni_http_tls_verified(nni_http *); -typedef struct nni_http_server nni_http_server; - -typedef struct { - // h_path is the relative URI that we are going to match against. - // Must not be NULL. Note that query parameters (things following - // a "?" at the end of the path) are ignored when matching. This - // field may not be NULL. - const char *h_path; - - // h_method is the HTTP method to handle such as "GET" or "POST". - // Must not be empty or NULL. If the incoming method is HEAD, then - // the server will process HEAD the same as GET, but will not send - // any response body. - const char *h_method; - - // h_host is used to match on a specific Host: entry. If left NULL, - // then this handler will match regardless of the Host: value. - const char *h_host; - - // h_is_dir indicates that the path represents a directory, and - // any path which is a logically below it should also be matched. - // This means that "/phone" will match for "/phone/bob" but not - // "/phoneme/ma". Be advised that it is not possible to register - // a handler for a parent and a different handler for children. - // (This restriction may be lifted in the future.) - bool h_is_dir; - - // h_is_upgrader is used for callbacks that "upgrade" (or steal) - // their connection. When this is true, the server framework - // assumes that the handler takes over *all* of the details of - // the connection. Consequently, the connection is disassociated - // from the framework, and no response is sent. (Upgraders are - // responsible for adopting the connection, including closing it - // when they are done, and for sending any HTTP response message. - // This is true even if an error occurs.) - bool h_is_upgrader; - - // h_cb is a callback that handles the request. The conventions - // are as follows: - // - // inputs: - // 0 - nni_http * for the actual underlying HTTP channel - // 1 - nni_http_req * for the HTTP request object - // 2 - void * for the opaque pointer supplied at registration - // - // outputs: - // 0 - (optional) nni_http_res * for an HTTP response (see below) - // - // The callback may choose to return the a response object in output 0, - // in which case the framework will handle sending the reply. - // (Response entity content is also sent if the response data - // is not NULL.) The callback may instead do it's own replies, in - // which case the response output should be NULL. - // - // Note that any request entity data is *NOT* supplied automatically - // with the request object; the callback is expected to call the - // nni_http_read_data method to retrieve any message data based upon - // the presence of headers. (It may also call nni_http_read or - // nni_http_write on the channel as it sees fit.) - // - // Upgraders should call the completion routine immediately, - // once they have collected the request object and HTTP channel. - void (*h_cb)(nni_aio *); -} nni_http_handler; +typedef struct nni_http_server nni_http_server; +typedef struct nni_http_handler nni_http_handler; // nni_http_server will look for an existing server with the same // name and port, or create one if one does not exist. The servers @@ -243,15 +181,15 @@ extern int nni_http_server_init(nni_http_server **, const char *); // all related resources. It will not affect hijacked connections. extern void nni_http_server_fini(nni_http_server *); -// nni_http_server_add_handler registers a new handler on the server. +// nni_http_server_add_handler registers a handler on the server. // This function will return NNG_EADDRINUSE if a conflicting handler // is already registered (i.e. a handler with the same value for Host, -// Method, and URL.) The first parameter receives an opaque handle to -// the handler, that can be used to unregister the handler later. -extern int nni_http_server_add_handler( - void **, nni_http_server *, nni_http_handler *, void *); +// Method, and URL.) +extern int nni_http_server_add_handler(nni_http_server *, nni_http_handler *); -extern void nni_http_server_del_handler(nni_http_server *, void *); +// nni_http_del_handler removes the given handler. The caller is +// responsible for finalizing it afterwards. +extern void nni_http_server_del_handler(nni_http_server *, nni_http_handler *); // nni_http_server_set_tls adds a TLS configuration to the server, // and enables the use of it. This returns NNG_EBUSY if the server is @@ -274,24 +212,114 @@ extern int nni_http_server_start(nni_http_server *); // associated with a callback will complete their callback, and then close. extern void nni_http_server_stop(nni_http_server *); -// nni_http_server_add_static is a short cut to add static -// content handler to the server. The host may be NULL, and the -// ctype (aka Content-Type) may be NULL. If the Content-Type is NULL, -// then application/octet stream will be the (probably bad) default. -// The actual data is copied, and so the caller may discard it once -// this function returns. -extern int nni_http_server_add_static(nni_http_server *, const char *host, - const char *ctype, const char *uri, const void *, size_t); - -// nni_http_server_add file is a short cut to add a file-backed static -// content handler to the server. The host may be NULL, and the -// ctype (aka Content-Type) may be NULL. If the Content-Type is NULL, -// then the server will try to guess it based on the filename -- but only -// a small number of file types are builtin. URI is the absolute URI -// (sans hostname and scheme), and the path is the path on the local -// filesystem where the file can be found. -extern int nni_http_server_add_file(nni_http_server *, const char *host, - const char *ctype, const char *uri, const char *path); +// nni_http_ctx is the context associated with a particular request +// arriving at the server, and is tied to an underlying nni_http channel. +typedef struct nni_http_ctx nni_http_ctx; + +// nni_http_hijack is intended to be called by a handler that wishes to +// take over the processing of the HTTP session -- usually to change protocols +// (such as in the case of websocket). The caller is responsible for obtaining +// and disposal of the associated nni_http session. Also, this completely +// disassociates the http session from the server, so the server may be +// stopped or destroyed without affecting the hijacked session. Note also +// that the hijacker will need to issue any HTTP reply itself. Finally, +// when a session is hijacked, the caller is also responsible for disposing +// of the request structure. (Some hijackers may keep the request for +// further processing.) +extern int nni_http_hijack(nni_http_ctx *); + +// nni_http_ctx_stream obtains the underlying nni_http channel for the +// context. This is used by hijackers, as well as anything that needs +// to handle sending its own replies on the channel. +extern int nni_http_ctx_stream(nni_http_ctx *, nni_http **); + +// nni_http_handler_init creates a server handler object, for the supplied +// URI (path only) with the callback. +// +// Note that methods which modify a handler cannot be called while the handler +// is registered with the server, and that a handler can only be registered +// once per server. +// +// The callback function will receive the following arguments (via +// nni_aio_get_input(): nni_http_request *, nni_http_handler *, and +// nni_http_context_t *. The first is a request object, for convenience. +// The second is the handler, from which the callback can obtain any other +// data it has set. The final is the http context, from which its possible +// to hijack the session. +extern int nni_http_handler_init( + nni_http_handler **, const char *, void (*)(nni_aio *)); + +// nni_http_handler_init_file creates a handler with a function to serve +// up a file named in the last argument. +extern int nni_http_handler_init_file( + nni_http_handler **, const char *, const char *); + +// nni_http_handler_init_file_ctype is like nni_http_handler_init_file, but +// provides for settign the Content-Type explicitly (last argument). +extern int nni_http_handler_init_file_ctype( + nni_http_handler **, const char *, const char *, const char *); + +// nni_http_handler_init_directory arranges to serve up an entire +// directory tree. The content types are determined from the builtin +// content type list. Actual directories are required to contain a +// file called index.html or index.htm. We do not generate directory +// listings for security reasons. +extern int nni_http_handler_init_directory( + nni_http_handler **, const char *, const char *); + +// nni_http_handler_init_static creates a handler that serves up static content +// supplied, with the Content-Type supplied in the final argument. +extern int nni_http_handler_init_static( + nni_http_handler **, const char *, const void *, size_t, const char *); + +// nni_http_handler_fini destroys a handler. This should only be done before +// the handler is added, or after it is deleted. The server automatically +// calls this for any handlers still registered with it if it is destroyed. +extern void nni_http_handler_fini(nni_http_handler *); + +// nni_http_handler_set_dtor sets a callback that is executed when +// the handler is torn down. The argument to the destructor is the +// handler itself. This function is called by the nni_http_handler_fini +// function. +extern int nni_http_handler_set_dtor( + nni_http_handler *, void (*)(nni_http_handler *)); + +// nni_http_handler_set_tree marks the handler as servicing the entire +// tree (e.g. a directory), rather than just a leaf node. The handler +// will probably need to inspect the URL of the request. +extern int nni_http_handler_set_tree(nni_http_handler *, bool); + +// nni_http_handler_set_host limits the handler to only being called for +// the given Host: field. This can be used to set up multiple virtual +// hosts. Note that host names must match exactly. If NULL or an empty +// string is specified, then the client's Host: field is ignored. (The +// supplied value for the Host is copied by this function.) When supplying +// a hostname, do not include a value for the port number; we do not match +// on port number as we assume that clients MUST have gotten that part right +// as we do not support virtual hosting on multiple separate ports; the +// server only listens on a single port. +extern int nni_http_handler_set_host(nni_http_handler *, const char *); + +// nni_http_handler_set_method limits the handler to only being called +// for the given HTTP method. By default a handler is called for GET +// methods only (and HEAD, which is handled internally.) Handlers can +// be specified for any valid HTTP method. A handler may set the value +// NULL here, to be called for any HTTP method. In such a case, the handler +// is obligated to inspect the method. (Note: the passed method must be +// in upper case and should come from a statically allocated string; the +// server does not make its own copy.) +extern int nni_http_handler_set_method(nni_http_handler *, const char *); + +// nni_http_handler_set_data sets an opaque data element on the handler, +// which will be available to the callback via nni_http_handler_get_data. +// Note that indices used should be small, to minimize array allocations. +// This can fail with NNG_ENOMEM if storage cannot be allocated. +extern int nni_http_handler_set_data(nni_http_handler *, void *, unsigned); + +// nni_http_handler_get_data returns the data that was previously stored +// at that index. It returns NULL if no data was set, or an invalid index +// is supplied. +extern void *nni_http_handler_get_data(nni_http_handler *, unsigned); // Client stuff. 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); } diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c index 514cf22d..df404b81 100644 --- a/src/supplemental/websocket/websocket.c +++ b/src/supplemental/websocket/websocket.c @@ -69,8 +69,7 @@ struct nni_ws_listener { nni_url * url; bool started; bool closed; - void * hp; // handler pointer - nni_http_handler handler; + nni_http_handler * handler; nni_ws_listen_hook hookfn; void * hookarg; nni_list headers; // response headers @@ -1333,6 +1332,9 @@ nni_ws_listener_fini(nni_ws_listener *l) } nni_mtx_unlock(&l->mtx); + if (l->handler != NULL) { + nni_http_handler_fini(l->handler); + } if (l->server != NULL) { nni_http_server_fini(l->server); l->server = NULL; @@ -1356,20 +1358,28 @@ nni_ws_listener_fini(nni_ws_listener *l) static void ws_handler(nni_aio *aio) { - nni_ws_listener *l; - nni_ws * ws; - nni_http * http; - nni_http_req * req; - nni_http_res * res; - const char * ptr; - const char * proto; - uint16_t status; - int rv; - char key[29]; - - http = nni_aio_get_input(aio, 0); - req = nni_aio_get_input(aio, 1); - l = nni_aio_get_input(aio, 2); + nni_ws_listener * l; + nni_ws * ws; + nni_http * http; + nni_http_req * req; + nni_http_res * res; + nni_http_handler *h; + nni_http_ctx * ctx; + const char * ptr; + const char * proto; + uint16_t status; + int rv; + char key[29]; + + req = nni_aio_get_input(aio, 0); + h = nni_aio_get_input(aio, 1); + ctx = nni_aio_get_input(aio, 2); + l = nni_http_handler_get_data(h, 0); + + if ((rv = nni_http_ctx_stream(ctx, &http)) != 0) { + nni_aio_finish_error(aio, rv); + return; + } // Now check the headers, etc. if (strcmp(nni_http_req_get_version(req), "HTTP/1.1") != 0) { @@ -1497,8 +1507,8 @@ ws_handler(nni_aio *aio) nni_list_append(&l->reply, ws); nni_aio_set_data(ws->httpaio, 0, l); nni_http_write_res(http, res, ws->httpaio); + (void) nni_http_hijack(ctx); nni_aio_set_output(aio, 0, NULL); - nni_aio_set_input(aio, 1, NULL); nni_aio_finish(aio, 0, 0); return; @@ -1535,7 +1545,7 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *addr) } host = l->url->u_hostname; - if ((strlen(host) == 0) || (strcmp(host, "*") == 0)) { + if (strlen(host) == 0) { host = NULL; } serv = l->url->u_port; @@ -1543,14 +1553,15 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *addr) serv = (strcmp(l->url->u_scheme, "wss") == 0) ? "443" : "80"; } - l->handler.h_is_dir = false; - l->handler.h_is_upgrader = true; - l->handler.h_method = "GET"; - l->handler.h_path = l->url->u_path; - l->handler.h_host = host; // ignore the port - l->handler.h_cb = ws_handler; + rv = nni_http_handler_init(&l->handler, l->url->u_path, ws_handler); + if (rv != 0) { + nni_ws_listener_fini(l); + return (rv); + } - if ((rv = nni_http_server_init(&l->server, addr)) != 0) { + if (((rv = nni_http_handler_set_host(l->handler, host)) != 0) || + ((rv = nni_http_handler_set_data(l->handler, l, 0)) != 0) || + ((rv = nni_http_server_init(&l->server, addr)) != 0)) { nni_ws_listener_fini(l); return (rv); } @@ -1632,7 +1643,7 @@ nni_ws_listener_close(nni_ws_listener *l) } l->closed = true; if (l->started) { - nni_http_server_del_handler(l->server, l->hp); + nni_http_server_del_handler(l->server, l->handler); nni_http_server_stop(l->server); l->started = false; } @@ -1660,8 +1671,7 @@ nni_ws_listener_listen(nni_ws_listener *l) return (NNG_ESTATE); } - rv = nni_http_server_add_handler(&l->hp, l->server, &l->handler, l); - if (rv != 0) { + if ((rv = nni_http_server_add_handler(l->server, l->handler)) != 0) { nni_http_server_fini(l->server); l->server = NULL; nni_mtx_unlock(&l->mtx); @@ -1669,7 +1679,7 @@ nni_ws_listener_listen(nni_ws_listener *l) } if ((rv = nni_http_server_start(l->server)) != 0) { - nni_http_server_del_handler(l->server, l->hp); + nni_http_server_del_handler(l->server, l->handler); nni_http_server_fini(l->server); l->server = NULL; nni_mtx_unlock(&l->mtx); diff --git a/tests/httpserver.c b/tests/httpserver.c index 147d0532..0d3f3710 100644 --- a/tests/httpserver.c +++ b/tests/httpserver.c @@ -18,11 +18,10 @@ // Basic HTTP server tests. #include "core/nng_impl.h" #include "supplemental/http/http.h" -#include "supplemental/sha1/sha1.h" -const uint8_t utf8_sha1sum[20] = { 0x54, 0xf3, 0xb8, 0xbb, 0xfe, 0xda, 0x6f, - 0xb4, 0x96, 0xdd, 0xc9, 0x8b, 0x8c, 0x41, 0xf4, 0xfe, 0xe5, 0xa9, 0x7d, - 0xa9 }; +const char *doc1 = "<html><body>Someone <b>is</b> home!</body</html>"; +const char *doc2 = "This is a text file."; +const char *doc3 = "<html><body>This is doc number 3.</body></html>"; void cleanup(void) @@ -30,29 +29,127 @@ cleanup(void) nng_fini(); } +static int +httpget(const char *addr, void **datap, size_t *sizep, uint16_t *statp, + char **ctypep) +{ + int rv; + nni_aio * aio = NULL; + nni_http_client *cli = NULL; + nni_http * h = NULL; + nni_http_req * req = NULL; + nni_http_res * res = NULL; + nni_url * url = NULL; + size_t clen = 0; + void * data = NULL; + char * ctype = NULL; + const char * ptr; + + if (((rv = nni_url_parse(&url, addr)) != 0) || + ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) || + ((rv = nni_http_req_init(&req)) != 0) || + ((rv = nni_http_res_init(&res)) != 0) || + ((rv = nni_http_client_init(&cli, addr)) != 0)) { + goto fail; + } + nni_http_client_connect(cli, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + goto fail; + } + + h = nni_aio_get_output(aio, 0); + if (((rv = nni_http_req_set_method(req, "GET")) != 0) || + ((rv = nni_http_req_set_version(req, "HTTP/1.1")) != 0) || + ((rv = nni_http_req_set_uri(req, url->u_path)) != 0) || + ((rv = nni_http_req_set_header(req, "Host", url->u_host)) != 0)) { + goto fail; + } + nni_http_write_req(h, req, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + goto fail; + } + nni_http_read_res(h, res, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + goto fail; + } + + *statp = nni_http_res_get_status(res); + clen = 0; + if ((*statp == NNI_HTTP_STATUS_OK) && + ((ptr = nni_http_res_get_header(res, "Content-Length")) != NULL)) { + clen = atoi(ptr); + } + + if (clen > 0) { + data = nni_alloc(clen); + aio->a_niov = 1; + aio->a_iov[0].iov_len = clen; + aio->a_iov[0].iov_buf = data; + nni_http_read_full(h, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + goto fail; + } + if ((ptr = nni_http_res_get_header(res, "Content-Type")) != + NULL) { + ctype = nni_strdup(ptr); + } + } + + *datap = data; + *sizep = clen; + *ctypep = ctype; + +fail: + if (rv != 0) { + if (data != NULL) { + nni_free(data, clen); + } + nni_strfree(ctype); + } + if (url != NULL) { + nni_url_free(url); + } + if (aio != NULL) { + nni_aio_fini(aio); + } + if (req != NULL) { + nni_http_req_fini(req); + } + if (res != NULL) { + nni_http_res_fini(res); + } + if (h != NULL) { + nni_http_fini(h); + } + if (cli != NULL) { + nni_http_client_fini(cli); + } + + return (rv); +} + TestMain("HTTP Client", { - nni_http_server *s; + nni_http_server * s; + nni_http_handler *h; nni_init(); atexit(cleanup); Convey("We can start an HTTP server", { - nng_sockaddr sa; - nni_aio * aio; - char portbuf[16]; - char url[32]; - char *doc = "<html><body>Someone <b>is</b> home!</body</html>"; + nni_aio *aio; + char portbuf[16]; + char url[32]; trantest_next_address(portbuf, "%u"); snprintf(url, sizeof(url), "http://127.0.0.1:%s", portbuf); So(nni_aio_init(&aio, NULL, NULL) == 0); - aio->a_addr = &sa; - nni_plat_tcp_resolv("127.0.0.1", portbuf, NNG_AF_INET, 0, aio); - nni_aio_wait(aio); - So(nni_aio_result(aio) == 0); So(nni_http_server_init(&s, url) == 0); @@ -61,8 +158,9 @@ TestMain("HTTP Client", { nni_http_server_fini(s); }); - So(nni_http_server_add_static(s, NULL, "text/html", - "/home.html", doc, strlen(doc)) == 0); + So(nni_http_handler_init_static(&h, "/home.html", doc1, + strlen(doc1), "text/html") == 0); + So(nni_http_server_add_handler(s, h) == 0); So(nni_http_server_start(s) == 0); Convey("We can connect a client to it", { @@ -132,18 +230,138 @@ TestMain("HTTP Client", { ptr = nni_http_res_get_header( res, "Content-Length"); So(ptr != NULL); - So(atoi(ptr) == strlen(doc)); + So(atoi(ptr) == strlen(doc1)); aio->a_niov = 1; - aio->a_iov[0].iov_len = strlen(doc); + aio->a_iov[0].iov_len = strlen(doc1); aio->a_iov[0].iov_buf = (void *) chunk; nni_http_read_full(h, aio); nni_aio_wait(aio); So(nni_aio_result(aio) == 0); - So(nni_aio_count(aio) == strlen(doc)); - So(memcmp(chunk, doc, strlen(doc)) == 0); + So(nni_aio_count(aio) == strlen(doc1)); + So(memcmp(chunk, doc1, strlen(doc1)) == 0); }); }); }); -}); + Convey("Directory serving works", { + nni_aio *aio; + char portbuf[16]; + char url[32]; + char * tmpdir; + char * workdir; + char * file1; + char * file2; + char * file3; + char * subdir1; + char * subdir2; + + trantest_next_address(portbuf, "%u"); + + snprintf(url, sizeof(url), "http://127.0.0.1:%s", portbuf); + + So(nni_aio_init(&aio, NULL, NULL) == 0); + So(nni_http_server_init(&s, url) == 0); + So((tmpdir = nni_plat_temp_dir()) != NULL); + So((workdir = nni_file_join(tmpdir, "httptest")) != NULL); + So((subdir1 = nni_file_join(workdir, "subdir1")) != NULL); + So((subdir2 = nni_file_join(workdir, "subdir2")) != NULL); + So((file1 = nni_file_join(subdir1, "index.html")) != NULL); + So((file2 = nni_file_join(workdir, "file.txt")) != NULL); + So((file3 = nni_file_join(subdir2, "index.htm")) != NULL); + + So(nni_file_put(file1, doc1, strlen(doc1)) == 0); + So(nni_file_put(file2, doc2, strlen(doc2)) == 0); + So(nni_file_put(file3, doc3, strlen(doc3)) == 0); + + Reset({ + nni_aio_fini(aio); + nni_http_server_fini(s); + nni_strfree(tmpdir); + nni_file_delete(file1); + nni_file_delete(file2); + nni_file_delete(file3); + nni_file_delete(subdir1); + nni_file_delete(subdir2); + nni_file_delete(workdir); + nni_strfree(workdir); + nni_strfree(file1); + nni_strfree(file2); + nni_strfree(file3); + nni_strfree(subdir1); + nni_strfree(subdir2); + }); + + So(nni_http_handler_init_directory(&h, "/docs", workdir) == 0); + So(nni_http_server_add_handler(s, h) == 0); + So(nni_http_server_start(s) == 0); + nng_msleep(100); + + Convey("Index.html works", { + char fullurl[256]; + void * data; + size_t size; + uint16_t stat; + char * ctype; + + snprintf(fullurl, sizeof(fullurl), + "%s/docs/subdir1/index.html", url); + So(httpget(fullurl, &data, &size, &stat, &ctype) == 0); + So(stat == NNI_HTTP_STATUS_OK); + So(size == strlen(doc1)); + So(memcmp(data, doc1, size) == 0); + So(strcmp(ctype, "text/html") == 0); + nni_strfree(ctype); + nni_free(data, size); + }); + + Convey("Index.htm works", { + char fullurl[256]; + void * data; + size_t size; + uint16_t stat; + char * ctype; + + snprintf( + fullurl, sizeof(fullurl), "%s/docs/subdir2", url); + So(httpget(fullurl, &data, &size, &stat, &ctype) == 0); + So(stat == NNI_HTTP_STATUS_OK); + So(size == strlen(doc3)); + So(memcmp(data, doc3, size) == 0); + So(strcmp(ctype, "text/html") == 0); + nni_strfree(ctype); + nni_free(data, size); + }); + + Convey("Named file works", { + char fullurl[256]; + void * data; + size_t size; + uint16_t stat; + char * ctype; + + snprintf( + fullurl, sizeof(fullurl), "%s/docs/file.txt", url); + So(httpget(fullurl, &data, &size, &stat, &ctype) == 0); + So(stat == NNI_HTTP_STATUS_OK); + So(size == strlen(doc2)); + So(memcmp(data, doc2, size) == 0); + So(strcmp(ctype, "text/plain") == 0); + nni_strfree(ctype); + nni_free(data, size); + }); + + Convey("Missing index gives 404", { + char fullurl[256]; + void * data; + size_t size; + uint16_t stat; + char * ctype; + + snprintf(fullurl, sizeof(fullurl), "%s/docs/", url); + So(httpget(fullurl, &data, &size, &stat, &ctype) == 0); + So(stat == NNI_HTTP_STATUS_NOT_FOUND); + So(size == 0); + }); + }); +}) |
