aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/file.c20
-rw-r--r--src/core/file.h8
-rw-r--r--src/platform/posix/posix_impl.h5
-rw-r--r--src/platform/windows/win_impl.h6
-rw-r--r--src/supplemental/http/http.h204
-rw-r--r--src/supplemental/http/server.c877
-rw-r--r--src/supplemental/websocket/websocket.c68
-rw-r--r--tests/httpserver.c260
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);
+ });
+ });
+})