aboutsummaryrefslogtreecommitdiff
path: root/src/supplemental
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-01-19 17:13:40 -0800
committerGarrett D'Amore <garrett@damore.org>2018-01-20 15:03:12 -0800
commit7dbc9ae4fef34c6fd836b939bd559e54b8008b72 (patch)
tree54660fef8a4a597c067a18953a295f1cfdb96cf0 /src/supplemental
parent338706c2420ce3e51b546a6ba2574e10346a511b (diff)
downloadnng-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')
-rw-r--r--src/supplemental/http/http.h204
-rw-r--r--src/supplemental/http/server.c877
-rw-r--r--src/supplemental/websocket/websocket.c68
3 files changed, 759 insertions, 390 deletions
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);