text: //
  // Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
  // Copyright 2018 Capitar IT Group BV <info@capitar.com>
  // Copyright 2018 QXSoftware <lh563566994@126.com>
  // Copyright 2019 Devolutions <info@devolutions.net>
  // Copyright 2020 Dirac Research <robert.bielik@dirac.com>
  //
  // This software is supplied under the terms of the MIT License, a
  // copy of which should be located in the distribution where this
  // file was obtained (LICENSE.txt).  A copy of the license may also be
  // found online at https://opensource.org/licenses/MIT.
  //
  
  #include <ctype.h>
  #include <stdbool.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  
  #include "core/nng_impl.h"
  #include "nng/supplemental/tls/tls.h"
  
  #include "http_api.h"
  
  struct nng_http_handler {
  	nni_list_node   node;
  	char *          uri;
  	char *          method;
  	char *          host;
  	nng_sockaddr    host_addr;
  	bool            host_ip;
  	bool            tree;
  	bool            tree_exclusive;
  	nni_atomic_u64  ref;
  	nni_atomic_bool busy;
  	size_t          maxbody;
  	bool            getbody;
  	void *          data;
  	nni_cb          dtor;
  	void (*cb)(nni_aio *);
  };
  
  typedef struct http_sconn {
  	nni_list_node     node;
  	nni_http_conn *   conn;
  	nni_http_server * server;
  	nni_http_req *    req;
  	nni_http_res *    res;
  	nni_http_handler *handler; // set if we deferred to read body
  	nni_http_handler *release; // set if we dispatched handler
  	bool              close;
  	bool              closed;
  	bool              finished;
  	nni_aio *         cbaio;
  	nni_aio *         rxaio;
  	nni_aio *         txaio;
  	nni_aio *         txdataio;
  	nni_reap_node     reap;
  } http_sconn;
  
  typedef struct http_error {
  	nni_list_node node;
  	uint16_t      code;
  	void *        body;
  	size_t        len;
  } http_error;
  
  struct nng_http_server {
  	nng_sockaddr         addr;
  	nni_list_node        node;
  	int                  refcnt;
  	int                  starts;
  	nni_list             handlers;
  	nni_list             conns;
  	nni_mtx              mtx;
  	nni_cv               cv;
  	bool                 closed;
  	nni_aio *            accaio;
  	nng_stream_listener *listener;
  	int                  port; // native order
  	char *               hostname;
  	nni_list             errors;
  	nni_mtx              errors_mtx;
  	nni_reap_node        reap;
  };
  
  static void http_sc_reap(void *);
  
  static nni_reap_list http_sc_reap_list = {
  	.rl_offset = offsetof(http_sconn, reap),
  	.rl_func   = http_sc_reap,
  };
  
  static void http_server_fini(nni_http_server *);
  
  static nni_reap_list http_server_reap_list = {
  	.rl_offset = offsetof(nni_http_server, reap),
  	.rl_func   = (nni_cb) http_server_fini,
  };
  
  int
  nni_http_handler_init(
      nni_http_handler **hp, const char *uri, void (*cb)(nni_aio *))
  {
  	nni_http_handler *h;
  
  	if ((h = NNI_ALLOC_STRUCT(h)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	nni_atomic_init64(&h->ref);
  	nni_atomic_inc64(&h->ref);
  
  	// Default for HTTP is /.  But remap it to "" for ease of matching.
  	if ((uri == NULL) || (strlen(uri) == 0) || (strcmp(uri, "/") == 0)) {
  		uri = "";
  	}
  	if (((h->uri = nni_strdup(uri)) == NULL) ||
  	    ((h->method = nni_strdup("GET")) == NULL)) {
  		nni_http_handler_fini(h);
  		return (NNG_ENOMEM);
  	}
  	NNI_LIST_NODE_INIT(&h->node);
  	h->cb             = cb;
  	h->data           = NULL;
  	h->dtor           = NULL;
  	h->host           = NULL;
  	h->tree           = false;
  	h->tree_exclusive = false;
  	h->maxbody = 1024 * 1024; // By default we accept up to 1MB of body
  	h->getbody = true;
  	*hp        = h;
  	return (0);
  }
  
  // nni_http_handler_fini just drops the reference count, only destroying
  // the handler if the reference drops to zero.
  void
  nni_http_handler_fini(nni_http_handler *h)
  {
  	if (nni_atomic_dec64_nv(&h->ref) != 0) {
  		return;
  	}
  	if (h->dtor != NULL) {
  		h->dtor(h->data);
  	}
  	nni_strfree(h->host);
  	nni_strfree(h->uri);
  	nni_strfree(h->method);
  	NNI_FREE_STRUCT(h);
  }
  
  void
  nni_http_handler_collect_body(nni_http_handler *h, bool want, size_t maxbody)
  {
  	h->getbody = want;
  	h->maxbody = maxbody;
  }
  
  int
  nni_http_handler_set_data(nni_http_handler *h, void *data, nni_cb dtor)
  {
  	if (nni_atomic_get_bool(&h->busy)) {
  		return (NNG_EBUSY);
  	}
  	h->data = data;
  	h->dtor = dtor;
  	return (0);
  }
  
  void *
  nni_http_handler_get_data(nni_http_handler *h)
  {
  	return (h->data);
  }
  
  const char *
  nni_http_handler_get_uri(nni_http_handler *h)
  {
  	if (strlen(h->uri) == 0) {
  		return ("/");
  	}
  	return (h->uri);
  }
  
  int
  nni_http_handler_set_tree(nni_http_handler *h)
  {
  	if (nni_atomic_get_bool(&h->busy) != 0) {
  		return (NNG_EBUSY);
  	}
  	h->tree           = true;
  	h->tree_exclusive = false;
  	return (0);
  }
  
  int
  nni_http_handler_set_tree_exclusive(nni_http_handler *h)
  {
  	if (nni_atomic_get_bool(&h->busy) != 0) {
  		return (NNG_EBUSY);
  	}
  	h->tree           = true;
  	h->tree_exclusive = true;
  	return (0);
  }
  
  int
  nni_http_handler_set_host(nni_http_handler *h, const char *host)
  {
  	char *dup;
  
  	if (nni_atomic_get_bool(&h->busy) != 0) {
  		return (NNG_EBUSY);
  	}
  	if ((host == NULL) || (strcmp(host, "*") == 0) ||
  	    strcmp(host, "") == 0) {
  		nni_strfree(h->host);
  		h->host = NULL;
  		return (0);
  	}
  	if (nni_parse_ip(host, &h->host_addr) == 0) {
  		uint8_t wild[16] = { 0 };
  
  		// Check for wild card addresses.
  		switch (h->host_addr.s_family) {
  		case NNG_AF_INET:
  			if (h->host_addr.s_in.sa_addr == 0) {
  				nni_strfree(h->host);
  				h->host = NULL;
  				return (0);
  			}
  			break;
  		case NNG_AF_INET6:
  			if (memcmp(h->host_addr.s_in6.sa_addr, wild, 16) ==
  			    0) {
  				nni_strfree(h->host);
  				h->host = NULL;
  				return (0);
  			}
  			break;
  		}
  		h->host_ip = true;
  	}
  	if ((dup = nni_strdup(host)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	nni_strfree(h->host);
  	h->host = dup;
  	return (0);
  }
  
  int
  nni_http_handler_set_method(nni_http_handler *h, const char *method)
  {
  	char *dup;
  
  	if (nni_atomic_get_bool(&h->busy) != 0) {
  		return (NNG_EBUSY);
  	}
  	if (method == NULL) {
  		nni_strfree(h->method);
  		h->method = NULL;
  		return (0);
  	}
  	if ((dup = nni_strdup(method)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	nni_strfree(h->method);
  	h->method = dup;
  	return (0);
  }
  
  static nni_list http_servers =
      NNI_LIST_INITIALIZER(http_servers, nni_http_server, node);
  static nni_mtx  http_servers_lk = NNI_MTX_INITIALIZER;
  
  static void
  http_sc_reap(void *arg)
  {
  	http_sconn *     sc = arg;
  	nni_http_server *s  = sc->server;
  	NNI_ASSERT(!sc->finished);
  	sc->finished = true;
  	nni_aio_stop(sc->rxaio);
  	nni_aio_stop(sc->txaio);
  	nni_aio_stop(sc->txdataio);
  	nni_aio_stop(sc->cbaio);
  
  	if (sc->conn != NULL) {
  		nni_http_conn_fini(sc->conn);
  	}
  	nni_http_req_free(sc->req);
  	nni_http_res_free(sc->res);
  	nni_aio_free(sc->rxaio);
  	nni_aio_free(sc->txaio);
  	nni_aio_free(sc->txdataio);
  	nni_aio_free(sc->cbaio);
  
  	// Now it is safe to release our reference on the server.
  	nni_mtx_lock(&s->mtx);
  	if (nni_list_node_active(&sc->node)) {
  		nni_list_remove(&s->conns, sc);
  	}
  	if (nni_list_empty(&s->conns)) {
  		nni_cv_wake(&s->cv);
  	}
  	nni_mtx_unlock(&s->mtx);
  
  	NNI_FREE_STRUCT(sc);
  }
  
  static void
  http_sc_close_locked(http_sconn *sc)
  {
  	nni_http_conn *conn;
  
  	if (sc->closed) {
  		return;
  	}
  	NNI_ASSERT(!sc->finished);
  
  	sc->closed = true;
  	nni_aio_close(sc->rxaio);
  	nni_aio_close(sc->txaio);
  	nni_aio_close(sc->txdataio);
  	nni_aio_close(sc->cbaio);
  
  	if ((conn = sc->conn) != NULL) {
  		nni_http_conn_close(conn);
  	}
  	nni_reap(&http_sc_reap_list, sc);
  }
  
  static void
  http_sconn_close(http_sconn *sc)
  {
  	nni_http_server *s;
  	s = sc->server;
  
  	nni_mtx_lock(&s->mtx);
  	http_sc_close_locked(sc);
  	nni_mtx_unlock(&s->mtx);
  }
  
  static void
  http_sconn_txdatdone(void *arg)
  {
  	http_sconn *sc  = arg;
  	nni_aio *   aio = sc->txdataio;
  
  	if (nni_aio_result(aio) != 0) {
  		http_sconn_close(sc);
  		return;
  	}
  
  	nni_http_res_free(sc->res);
  	sc->res = NULL;
  
  	if (sc->close) {
  		http_sconn_close(sc);
  		return;
  	}
  
  	sc->handler = NULL;
  	nni_http_req_reset(sc->req);
  	nni_http_read_req(sc->conn, sc->req, sc->rxaio);
  }
  
  static void
  http_sconn_txdone(void *arg)
  {
  	http_sconn *sc  = arg;
  	nni_aio *   aio = sc->txaio;
  
  	if (nni_aio_result(aio) != 0) {
  		http_sconn_close(sc);
  		return;
  	}
  
  	if (sc->close) {
  		http_sconn_close(sc);
  		return;
  	}
  
  	nni_http_res_free(sc->res);
  	sc->res     = NULL;
  	sc->handler = NULL;
  	nni_http_req_reset(sc->req);
  	nni_http_read_req(sc->conn, sc->req, sc->rxaio);
  }
  
  static char
  http_hexval(char c)
  {
  	if ((c >= '0') && (c <= '9')) {
  		return (c - '0');
  	}
  	if ((c >= 'a') && (c <= 'f')) {
  		return ((c - 'a') + 10);
  	}
  	if ((c >= 'A') && (c <= 'F')) {
  		return ((c - 'A') + 10);
  	}
  	return (0);
  }
  
  // XXX: REPLACE THIS WITH CODE USING THE URL FRAMEWORK.
  static char *
  http_uri_canonify(char *path)
  {
  	char *tmp;
  	char *dst;
  
  	// Chomp off query string.
  	if ((tmp = strchr(path, '?')) != NULL) {
  		*tmp = '\0';
  	}
  	// If the URI was absolute, make it relative.
  	if ((nni_strncasecmp(path, "http://", strlen("http://")) == 0) ||
  	    (nni_strncasecmp(path, "https://", strlen("https://")) == 0)) {
  		// Skip past the ://
  		path = strchr(path, ':');
  		path += 3;
  
  		// scan for the end of the host, distinguished by a /
  		// path delimiter.  There might not be one, in which
  		// case the whole thing is the host and we assume the
  		// path is just /.
  		if ((path = strchr(path, '/')) == NULL) {
  			return ("/");
  		}
  	}
  
  	// Now we have to unescape things.  Unescaping is a shrinking
  	// operation (strictly), so this is safe.  This is just URL
  	// decode. Note that paths with an embedded NUL are going to be
  	// treated as though truncated.  Don't be that guy that sends
  	// %00 in a URL.
  	//
  	// XXX: Normalizer needs to leave % encoded stuff in there if
  	// the characters to which they refer are reserved.  See RFC 3986
  	// section 6.2.2.
  	tmp = path;
  	dst = path;
  	while (*tmp != '\0') {
  		char c;
  		if ((c = *tmp) != '%') {
  			*dst++ = c;
  			tmp++;
  			continue;
  		}
  		if (isxdigit(tmp[1]) && isxdigit(tmp[2])) {
  			c = http_hexval(tmp[1]);
  			c *= 16;
  			c += http_hexval(tmp[2]);
  			*dst++ = c;
  			tmp += 3;
  		}
  		// garbage in, garbage out
  		*dst++ = c;
  		tmp++;
  	}
  	*dst = '\0';
  
  	return ((strlen(path) != 0) ? path : "/");
  }
  
  static void
  http_sconn_error(http_sconn *sc, uint16_t err)
  {
  	nni_http_res *res;
  
  	if (nni_http_res_alloc(&res) != 0) {
  		http_sconn_close(sc);
  		return;
  	}
  	nni_http_res_set_status(res, err);
  	if (nni_http_server_res_error(sc->server, res) != 0) {
  		nni_http_res_free(res);
  		http_sconn_close(sc);
  		return;
  	}
  
  	if (sc->close) {
  		if (nni_http_res_set_header(res, "Connection", "close") != 0) {
  			nni_http_res_free(res);
  			http_sconn_close(sc);
  		}
  	}
  	sc->res = res;
  	nni_http_write_res(sc->conn, res, sc->txaio);
  }
  
  int
  nni_http_hijack(nni_http_conn *conn)
  {
  	http_sconn *sc;
  
  	sc = nni_http_conn_get_ctx(conn);
  	if (sc != NULL) {
  		nni_http_server *s = sc->server;
  		nni_http_conn_set_ctx(conn, NULL);
  
  		nni_mtx_lock(&s->mtx);
  		sc->conn = NULL;
  		sc->req  = NULL;
  		nni_mtx_unlock(&s->mtx);
  	}
  	return (0);
  }
  
  static bool
  http_handler_host_match(nni_http_handler *h, const char *host)
  {
  	nng_sockaddr sa;
  	size_t       len;
  
  	if (h->host == NULL) {
  		return (true);
  	}
  	if (host == NULL) {
  		// Virtual hosts not possible under HTTP/1.0
  		return (false);
  	}
  	if (h->host_ip) {
  		if (nni_parse_ip_port(host, &sa) != 0) {
  			return (false);
  		}
  		switch (h->host_addr.s_family) {
  		case NNG_AF_INET:
  			if ((sa.s_in.sa_family != NNG_AF_INET) ||
  			    (sa.s_in.sa_addr != h->host_addr.s_in.sa_addr)) {
  				return (false);
  			}
  			return (true);
  		case NNG_AF_INET6:
  			if (sa.s_in6.sa_family != NNG_AF_INET6) {
  				return (false);
  			}
  			if (memcmp(sa.s_in6.sa_addr,
  			        h->host_addr.s_in6.sa_addr, 16) != 0) {
  				return (false);
  			}
  			return (true);
  		}
  	}
  
  	len = strlen(h->host);
  
  	if ((nni_strncasecmp(host, h->host, len) != 0)) {
  		return (false);
  	}
  
  	// 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'))) {
  		return (false);
  	}
  
  	return (true);
  }
  
  static void
  http_sconn_rxdone(void *arg)
  {
  	http_sconn *      sc  = arg;
  	nni_http_server * s   = sc->server;
  	nni_aio *         aio = sc->rxaio;
  	int               rv;
  	nni_http_handler *h    = NULL;
  	nni_http_handler *head = NULL;
  	const char *      val;
  	nni_http_req *    req = sc->req;
  	char *            uri;
  	size_t            urisz;
  	char *            path;
  	bool              badmeth  = false;
  	bool              needhost = false;
  	const char *      host;
  	const char *      cls;
  
  	if ((rv = nni_aio_result(aio)) != 0) {
  		http_sconn_close(sc);
  		return;
  	}
  
  	if ((h = sc->handler) != NULL) {
  		nni_mtx_lock(&s->mtx);
  		goto finish;
  	}
  
  	// 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, NNG_HTTP_STATUS_BAD_REQUEST);
  		return;
  	}
  	if (strncmp(val, "HTTP/1.", 7) != 0) {
  		sc->close = true;
  		http_sconn_error(sc, NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP);
  		return;
  	}
  	if (strcmp(val, "HTTP/1.1") != 0) {
  		// We treat HTTP/1.0 connections as non-persistent.
  		// No effort is made for non-standard "persistent" HTTP/1.0.
  		sc->close = true;
  	} else {
  		needhost = true;
  	}
  
  	// If the connection was 1.0, or a connection: close was
  	// requested, then mark this close on our end.
  	if ((val = nni_http_req_get_header(req, "Connection")) != NULL) {
  		// HTTP 1.1 says these have to be case insensitive
  		if (nni_strcasestr(val, "close") != NULL) {
  			// In theory this could falsely match some other weird
  			// connection header with the substring close.  No such
  			// values are defined, so anyone who does that gets
  			// what they deserve. (Harmless actually, since it only
  			// prevents persistent connections.)
  			sc->close = true;
  		}
  	}
  
  	val   = nni_http_req_get_uri(req);
  	urisz = strlen(val) + 1;
  	if ((uri = nni_alloc(urisz)) == NULL) {
  		http_sconn_close(sc); // out of memory
  		return;
  	}
  	strncpy(uri, val, urisz);
  	path = http_uri_canonify(uri);
  
  	host = nni_http_req_get_header(req, "Host");
  	if ((host == NULL) && (needhost)) {
  		// Per RFC 2616 14.23 we have to send 400 status here.
  		http_sconn_error(sc, NNG_HTTP_STATUS_BAD_REQUEST);
  		nni_free(uri, urisz);
  		return;
  	}
  
  	nni_mtx_lock(&s->mtx);
  	NNI_LIST_FOREACH (&s->handlers, h) {
  		size_t len;
  
  		if (!http_handler_host_match(h, host)) {
  			continue;
  		}
  
  		len = strlen(h->uri);
  		if (strncmp(path, h->uri, len) != 0) {
  			continue;
  		}
  		switch (path[len]) {
  		case '\0':
  			break;
  		case '/':
  			if ((path[len + 1] != '\0') && (!h->tree)) {
  				// Trailing component and not a directory.
  				continue;
  			}
  			break;
  		default:
  			continue; // Some other substring, not matched.
  		}
  
  		if ((h->method == NULL) || (h->method[0] == '\0')) {
  			// Handler wants to process *all* methods.
  			break;
  		}
  		// So, what about the method?
  		val = nni_http_req_get_method(req);
  		if (strcmp(val, h->method) == 0) {
  			break;
  		}
  		// HEAD is remapped to GET, but only if no HEAD specific
  		// handler registered.
  		if ((strcmp(val, "HEAD") == 0) &&
  		    (strcmp(h->method, "GET") == 0)) {
  			head = h;
  			continue;
  		}
  		badmeth = 1;
  	}
  
  	if ((h == NULL) && (head != NULL)) {
  		h = head;
  	}
  	nni_free(uri, urisz);
  	if (h == NULL) {
  		nni_mtx_unlock(&s->mtx);
  		if (badmeth) {
  			http_sconn_error(
  			    sc, NNG_HTTP_STATUS_METHOD_NOT_ALLOWED);
  		} else {
  			http_sconn_error(sc, NNG_HTTP_STATUS_NOT_FOUND);
  		}
  		return;
  	}
  
  	if ((h->getbody) &&
  	    ((cls = nni_http_req_get_header(req, "Content-Length")) != NULL)) {
  		uint64_t len;
  		char *   end;
  
  		len = strtoull(cls, &end, 10);
  		if ((end == NULL) || (*end != '\0') || (len > h->maxbody)) {
  			nni_mtx_unlock(&s->mtx);
  			http_sconn_error(sc, NNG_HTTP_STATUS_BAD_REQUEST);
  			return;
  		}
  		if (len > 0) {
  			nng_iov iov;
  			if ((nni_http_req_alloc_data(req, (size_t) len)) !=
  			    0) {
  				nni_mtx_unlock(&s->mtx);
  				http_sconn_error(
  				    sc, NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR);
  				return;
  			}
  			nng_http_req_get_data(req, &iov.iov_buf, &iov.iov_len);
  			sc->handler = h;
  			nni_mtx_unlock(&s->mtx);
  			nni_aio_set_iov(sc->rxaio, 1, &iov);
  			nni_http_read_full(sc->conn, aio);
  			return;
  		}
  	}
  
  finish:
  	sc->release = h;
  	sc->handler = NULL;
  	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->conn);
  
  	// Set a reference -- this because the callback may be running
  	// asynchronously even after it gets removed from the server.
  	nni_atomic_inc64(&h->ref);
  
  	// Documented that we call this on behalf of the callback.
  	if (nni_aio_begin(sc->cbaio) != 0) {
  		nni_mtx_unlock(&s->mtx);
  		return;
  	}
  
  	nni_mtx_unlock(&s->mtx);
  	h->cb(sc->cbaio);
  }
  
  static void
  http_sconn_cbdone(void *arg)
  {
  	http_sconn *      sc  = arg;
  	nni_aio *         aio = sc->cbaio;
  	nni_http_res *    res;
  	nni_http_handler *h;
  	nni_http_server * s = sc->server;
  
  	// Get the handler.  It may be set regardless of success or
  	// failure.  Clear it, and drop our reference, since we're
  	// done with the handler for now.
  	if ((h = sc->release) != NULL) {
  		sc->release = NULL;
  		nni_http_handler_fini(h);
  	}
  
  	if (nni_aio_result(aio) != 0) {
  		// Hard close, no further feedback.
  		http_sconn_close(sc);
  		return;
  	}
  
  	res = nni_aio_get_output(aio, 0);
  
  	// If it's 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->conn == NULL) {
  		// If this happens, then the session was hijacked.
  		// We close the context, but the http channel stays up.
  		http_sconn_close(sc);
  		return;
  	}
  	if (res != NULL) {
  		const char *val;
  		val = nni_http_res_get_header(res, "Connection");
  		if ((val != NULL) && (strstr(val, "close") != NULL)) {
  			sc->close = true;
  		}
  		if (sc->close) {
  			nni_http_res_set_header(res, "Connection", "close");
  		}
  		sc->res = res;
  		if (strcmp(nni_http_req_get_method(sc->req), "HEAD") == 0) {
  			void * data;
  			size_t size;
  			// prune off the data, but preserve the content-length
  			// header.  By passing NULL here, we leave off the old
  			// data, but the non-zero size means we don't clobber
  			// the HTTP header.
  			nni_http_res_get_data(res, &data, &size);
  			nni_http_res_set_data(res, NULL, size);
  		} else if (nni_http_res_is_error(res)) {
  			(void) nni_http_server_res_error(s, res);
  		}
  		nni_http_write_res(sc->conn, res, sc->txaio);
  	} else if (sc->close) {
  		http_sconn_close(sc);
  	} else {
  		// Presumably client already sent a response.
  		// Wait for another request.
  		sc->handler = NULL;
  		nni_http_req_reset(sc->req);
  		nni_http_read_req(sc->conn, sc->req, sc->rxaio);
  	}
  }
  
  static int
  http_sconn_init(http_sconn **scp, nng_stream *stream)
  {
  	http_sconn *sc;
  	int         rv;
  
  	if ((sc = NNI_ALLOC_STRUCT(sc)) == NULL) {
  		nng_stream_free(stream);
  		return (NNG_ENOMEM);
  	}
  
  	if (((rv = nni_http_req_alloc(&sc->req, NULL)) != 0) ||
  	    ((rv = nni_aio_alloc(&sc->rxaio, http_sconn_rxdone, sc)) != 0) ||
  	    ((rv = nni_aio_alloc(&sc->txaio, http_sconn_txdone, sc)) != 0) ||
  	    ((rv = nni_aio_alloc(&sc->txdataio, http_sconn_txdatdone, sc)) !=
  	        0) ||
  	    ((rv = nni_aio_alloc(&sc->cbaio, http_sconn_cbdone, sc)) != 0)) {
  		// Can't even accept the incoming request.  Hard close.
  		http_sconn_close(sc);
  		return (rv);
  	}
  
  	rv = nni_http_conn_init(&sc->conn, stream);
  	if (rv != 0) {
  		http_sconn_close(sc);
  		return (rv);
  	}
  	nni_http_conn_set_ctx(sc->conn, sc);
  	*scp = sc;
  	return (0);
  }
  
  static void
  http_server_acccb(void *arg)
  {
  	nni_http_server *s   = arg;
  	nni_aio *        aio = s->accaio;
  	nng_stream *     stream;
  	http_sconn *     sc;
  	int              rv;
  
  	nni_mtx_lock(&s->mtx);
  	if ((rv = nni_aio_result(aio)) != 0) {
  		if (!s->closed) {
  			// try again?
  			nng_stream_listener_accept(s->listener, s->accaio);
  		}
  		nni_mtx_unlock(&s->mtx);
  		return;
  	}
  	stream = nni_aio_get_output(aio, 0);
  	if (s->closed) {
  		// If we're closing, then reject this one.
  		nng_stream_free(stream);
  		nni_mtx_unlock(&s->mtx);
  		return;
  	}
  	if (http_sconn_init(&sc, stream) != 0) {
  		// The stream structure is already cleaned up.
  		// Start another accept attempt.
  		nng_stream_listener_accept(s->listener, s->accaio);
  		nni_mtx_unlock(&s->mtx);
  		return;
  	}
  	sc->server = s;
  	nni_list_append(&s->conns, sc);
  
  	sc->handler = NULL;
  	nni_http_read_req(sc->conn, sc->req, sc->rxaio);
  	nng_stream_listener_accept(s->listener, s->accaio);
  	nni_mtx_unlock(&s->mtx);
  }
  
  static void
  http_server_fini(nni_http_server *s)
  {
  	nni_http_handler *h;
  	http_error *      epage;
  
  	nni_aio_stop(s->accaio);
  
  	nni_mtx_lock(&s->mtx);
  	if (!nni_list_empty(&s->conns)) {
  		// Try to reap later, after the connections are done reaping.
  		// (Note, connections will all have been closed already.)
  		nni_reap(&http_server_reap_list, s);
  		nni_mtx_unlock(&s->mtx);
  		return;
  	}
  	nng_stream_listener_free(s->listener);
  	while ((h = nni_list_first(&s->handlers)) != NULL) {
  		nni_list_remove(&s->handlers, h);
  		nni_http_handler_fini(h);
  	}
  	nni_mtx_unlock(&s->mtx);
  	nni_mtx_lock(&s->errors_mtx);
  	while ((epage = nni_list_first(&s->errors)) != NULL) {
  		nni_list_remove(&s->errors, epage);
  		nni_free(epage->body, epage->len);
  		NNI_FREE_STRUCT(epage);
  	}
  	nni_mtx_unlock(&s->errors_mtx);
  	nni_mtx_fini(&s->errors_mtx);
  
  	nni_aio_free(s->accaio);
  	nni_cv_fini(&s->cv);
  	nni_mtx_fini(&s->mtx);
  	nni_strfree(s->hostname);
  	NNI_FREE_STRUCT(s);
  }
  
  static int
  http_server_init(nni_http_server **serverp, const nni_url *url)
  {
  	nni_http_server *s;
  	int              rv;
  	nng_url          my_url;
  	const char *     scheme;
  
  	if ((scheme = nni_http_stream_scheme(url->u_scheme)) == NULL) {
  		return (NNG_EADDRINVAL);
  	}
  	// Rewrite URLs to either TLS or TCP.
  	memcpy(&my_url, url, sizeof(my_url));
  	my_url.u_scheme = (char *) scheme;
  
  	if ((s = NNI_ALLOC_STRUCT(s)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	nni_mtx_init(&s->mtx);
  	nni_mtx_init(&s->errors_mtx);
  	nni_cv_init(&s->cv, &s->mtx);
  	NNI_LIST_INIT(&s->handlers, nni_http_handler, node);
  	NNI_LIST_INIT(&s->conns, http_sconn, node);
  
  	nni_mtx_init(&s->errors_mtx);
  	NNI_LIST_INIT(&s->errors, http_error, node);
  
  	if ((rv = nni_aio_alloc(&s->accaio, http_server_acccb, s)) != 0) {
  		http_server_fini(s);
  		return (rv);
  	}
  
  	// NB: We only support number port numbers, and the URL framework
  	// expands empty port numbers to 80 or 443 as appropriate.
  	s->port = atoi(url->u_port);
  
  	if ((s->hostname = nni_strdup(url->u_hostname)) == NULL) {
  		http_server_fini(s);
  		return (NNG_ENOMEM);
  	}
  
  	if ((rv = nng_stream_listener_alloc_url(&s->listener, &my_url)) != 0) {
  		http_server_fini(s);
  		return (rv);
  	}
  
  	s->refcnt = 1;
  	*serverp  = s;
  	return (0);
  }
  
  int
  nni_http_server_init(nni_http_server **serverp, const nni_url *url)
  {
  	int              rv;
  	nni_http_server *s;
  
  	nni_mtx_lock(&http_servers_lk);
  	NNI_LIST_FOREACH (&http_servers, s) {
  		if ((!s->closed) && (atoi(url->u_port) == s->port) &&
  		    (strcmp(url->u_hostname, s->hostname) == 0)) {
  			*serverp = s;
  			s->refcnt++;
  			nni_mtx_unlock(&http_servers_lk);
  			return (0);
  		}
  	}
  
  	// We didn't find a server, try to make a new one.
  	if ((rv = http_server_init(&s, url)) == 0) {
  		nni_list_append(&http_servers, s);
  		*serverp = s;
  	}
  
  	nni_mtx_unlock(&http_servers_lk);
  	return (rv);
  }
  
  static int
  http_server_start(nni_http_server *s)
  {
  	int rv;
  	if ((rv = nng_stream_listener_listen(s->listener)) != 0) {
  		return (rv);
  	}
  	if (s->port == 0) {
  		nng_stream_listener_get_int(
  		    s->listener, NNG_OPT_TCP_BOUND_PORT, &s->port);
  	}
  	nng_stream_listener_accept(s->listener, s->accaio);
  	return (0);
  }
  
  int
  nni_http_server_start(nni_http_server *s)
  {
  	int rv = 0;
  
  	nni_mtx_lock(&s->mtx);
  	if (s->starts == 0) {
  		rv = http_server_start(s);
  	}
  	if (rv == 0) {
  		s->starts++;
  	}
  	nni_mtx_unlock(&s->mtx);
  	return (rv);
  }
  
  static void
  http_server_stop(nni_http_server *s)
  {
  	http_sconn *sc;
  
  	if (s->closed) {
  		return;
  	}
  	s->closed = true;
  
  	nni_aio_close(s->accaio);
  
  	// Close the TCP endpoint that is listening.
  	if (s->listener) {
  		nng_stream_listener_close(s->listener);
  	}
  
  	// 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_sc_close_locked(sc);
  	}
  
  	while (!nni_list_empty(&s->conns)) {
  		nni_cv_wait(&s->cv);
  	}
  }
  
  void
  nni_http_server_stop(nni_http_server *s)
  {
  	nni_mtx_lock(&s->mtx);
  	s->starts--;
  	if (s->starts == 0) {
  		http_server_stop(s);
  	}
  	nni_mtx_unlock(&s->mtx);
  }
  
  static int
  http_server_set_err(nni_http_server *s, uint16_t code, void *body, size_t len)
  {
  	http_error *epage;
  
  	nni_mtx_lock(&s->errors_mtx);
  	NNI_LIST_FOREACH (&s->errors, epage) {
  		if (epage->code == code) {
  			break;
  		}
  	}
  	if (epage == NULL) {
  		if ((epage = NNI_ALLOC_STRUCT(epage)) == NULL) {
  			nni_mtx_unlock(&s->mtx);
  			return (NNG_ENOMEM);
  		}
  		epage->code = code;
  		nni_list_append(&s->errors, epage);
  	}
  	if (epage->len != 0) {
  		nni_free(epage->body, epage->len);
  	}
  	epage->body = body;
  	epage->len  = len;
  	nni_mtx_unlock(&s->errors_mtx);
  	return (0);
  }
  
  int
  nni_http_server_set_error_page(
      nni_http_server *s, uint16_t code, const char *html)
  {
  	char * body;
  	int    rv;
  	size_t len;
  
  	// We copy the content, without the trailing NUL.
  	len = strlen(html);
  	if ((body = nni_alloc(len)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	memcpy(body, html, len);
  	if ((rv = http_server_set_err(s, code, body, len)) != 0) {
  		nni_free(body, len);
  	}
  	return (rv);
  }
  
  int
  nni_http_server_set_error_file(
      nni_http_server *s, uint16_t code, const char *path)
  {
  	void * body;
  	size_t len;
  	int    rv;
  	if ((rv = nni_file_get(path, &body, &len)) != 0) {
  		return (rv);
  	}
  	if ((rv = http_server_set_err(s, code, body, len)) != 0) {
  		nni_free(body, len);
  	}
  	return (rv);
  }
  
  int
  nni_http_server_res_error(nni_http_server *s, nni_http_res *res)
  {
  	http_error *epage;
  	char *      body = NULL;
  	char *      html = NULL;
  	size_t      len;
  	uint16_t    code = nni_http_res_get_status(res);
  	int         rv;
  
  	nni_mtx_lock(&s->errors_mtx);
  	NNI_LIST_FOREACH (&s->errors, epage) {
  		if (epage->code == code) {
  			body = epage->body;
  			len  = epage->len;
  			break;
  		}
  	}
  	nni_mtx_unlock(&s->errors_mtx);
  
  	if (body == NULL) {
  		if ((rv = nni_http_alloc_html_error(&html, code, NULL)) != 0) {
  			return (rv);
  		}
  		body = html;
  		len  = strlen(body);
  	}
  
  	// NB: The server lock has to be held here to guard against the
  	// error page being tossed or changed.
  	if (((rv = nni_http_res_copy_data(res, body, len)) == 0) &&
  	    ((rv = nni_http_res_set_header(
  	          res, "Content-Type", "text/html; charset=UTF-8")) == 0)) {
  		nni_http_res_set_status(res, code);
  	}
  	nni_strfree(html);
  
  	return (rv);
  }
  
  int
  nni_http_server_add_handler(nni_http_server *s, nni_http_handler *h)
  {
  	nni_http_handler *h2;
  	size_t            len;
  
  	// Must have a legal method (and not one that is HEAD), path,
  	// and handler.  (The reason HEAD is verboten is that we supply
  	// it automatically as part of GET support.)
  	if ((((len = strlen(h->uri)) > 0) && (h->uri[0] != '/')) ||
  	    (h->cb == NULL)) {
  		return (NNG_EINVAL);
  	}
  	while ((len > 0) && (h->uri[len - 1] == '/')) {
  		len--; // ignore trailing '/' (this collapses them)
  	}
  
  	nni_mtx_lock(&s->mtx);
  	// General rule for finding a conflict is that if either uri
  	// string is an exact duplicate of the other, then we have a
  	// collision.  (But only if the methods match, and the host
  	// matches.)  Note that a wild card host matches both.
  	NNI_LIST_FOREACH (&s->handlers, h2) {
  		size_t len2;
  
  		if ((h2->host != NULL) && (h->host != NULL) &&
  		    (nni_strcasecmp(h2->host, h->host) != 0)) {
  			// Hosts don't match, so we are safe.
  			continue;
  		}
  		if (((h2->host == NULL) && (h->host != NULL)) ||
  		    ((h->host == NULL) && (h2->host != NULL))) {
  			continue; // Host specified for just one.
  		}
  		if (((h->method == NULL) && (h2->method != NULL)) ||
  		    ((h2->method == NULL) && (h->method != NULL))) {
  			continue; // Method specified for just one.
  		}
  		if ((h->method != NULL) &&
  		    (strcmp(h2->method, h->method) != 0)) {
  			// Different methods, so again we are fine.
  			continue;
  		}
  
  		len2 = strlen(h2->uri);
  
  		while ((len2 > 0) && (h2->uri[len2 - 1] == '/')) {
  			len2--; // ignore trailing '/'
  		}
  
  		if ((h2->tree && h2->tree_exclusive) ||
  		    (h->tree && h->tree_exclusive)) {
  			// Old behavior
  			if (strncmp(h->uri, h2->uri,
  			        len > len2 ? len2 : len) != 0) {
  				continue; // prefixes don't match.
  			}
  
  			if (len2 > len) {
  				if ((h2->uri[len] == '/') && (h->tree)) {
  					nni_mtx_unlock(&s->mtx);
  					return (NNG_EADDRINUSE);
  				}
  			} else if (len > len2) {
  				if ((h->uri[len2] == '/') && (h2->tree)) {
  					nni_mtx_unlock(&s->mtx);
  					return (NNG_EADDRINUSE);
  				}
  			} else {
  				nni_mtx_unlock(&s->mtx);
  				return (NNG_EADDRINUSE);
  			}
  		} else {
  			if (len != len2) {
  				continue; // length mismatch
  			}
  
  			if (strcmp(h->uri, h2->uri) != 0) {
  				continue; // not a duplicate
  			}
  
  			nni_mtx_unlock(&s->mtx);
  			return (NNG_EADDRINUSE);
  		}
  	}
  
  	// Maintain list of handlers in longest uri first order
  	NNI_LIST_FOREACH (&s->handlers, h2) {
  		size_t len2 = strlen(h2->uri);
  		if (len > len2) {
  			nni_list_insert_before(&s->handlers, h, h2);
  			break;
  		}
  	}
  	if (h2 == NULL) {
  		nni_list_append(&s->handlers, h);
  	}
  
  	// Note that we have borrowed the reference count on the handler.
  	// Thus we own it, and if the server is destroyed while we have it,
  	// then we must finalize it it too.  We do mark it busy so
  	// that other settings cannot change.
  	nni_atomic_set_bool(&h->busy, true);
  
  	nni_mtx_unlock(&s->mtx);
  	return (0);
  }
  
  int
  nni_http_server_del_handler(nni_http_server *s, nni_http_handler *h)
  {
  	int               rv = NNG_ENOENT;
  	nni_http_handler *srch;
  	nni_mtx_lock(&s->mtx);
  	NNI_LIST_FOREACH (&s->handlers, srch) {
  		if (srch == h) {
  			// NB: We are giving the caller our reference
  			// on the handler.
  			nni_list_remove(&s->handlers, h);
  			rv = 0;
  			break;
  		}
  	}
  	nni_mtx_unlock(&s->mtx);
  
  	return (rv);
  }
  
  // Very limited MIME type map.  Used only if the handler does not
  // supply it's own.
  static struct content_map {
  	const char *ext;
  	const char *typ;
  } content_map[] = {
  	// clang-format off
  	{ ".ai", "application/postscript" },
  	{ ".aif", "audio/aiff" },
  	{ ".aiff", "audio/aiff" },
  	{ ".avi", "video/avi" },
  	{ ".au", "audio/basic" },
  	{ ".bin", "application/octet-stream" },
  	{ ".bmp", "image/bmp" },
  	{ ".css", "text/css" },
  	{ ".eps", "application/postscript" },
  	{ ".gif", "image/gif" },
  	{ ".htm", "text/html" },
  	{ ".html", "text/html" },
  	{ ".ico", "image/x-icon" },
  	{ ".jpeg", "image/jpeg" },
  	{ ".jpg", "image/jpeg" },
  	{ ".js", "application/javascript" },
  	{ ".md", "text/markdown" },
  	{ ".mp2", "video/mpeg" },
  	{ ".mp3", "audio/mpeg3" },
  	{ ".mpeg", "video/mpeg" },
  	{ ".mpg", "video/mpeg" },
  	{ ".pdf", "application/pdf" },
  	{ ".png", "image/png" },
  	{ ".ps", "application/postscript" },
  	{ ".rtf", "text/rtf" },
  	{ ".text", "text/plain" },
  	{ ".tif", "image/tiff" },
  	{ ".tiff", "image/tiff" },
  	{ ".txt", "text/plain" },
  	{ ".wav", "audio/wav"},
  	{ "README", "text/plain" },
  	{ NULL, NULL },
  	// clang-format on
  };
  
  const char *
  http_lookup_type(const char *path)
  {
  	size_t l1 = strlen(path);
  	for (int i = 0; content_map[i].ext != NULL; i++) {
  		size_t l2 = strlen(content_map[i].ext);
  		if (l2 > l1) {
  			continue;
  		}
  		if (nni_strcasecmp(&path[l1 - l2], content_map[i].ext) == 0) {
  			return (content_map[i].typ);
  		}
  	}
  	return (NULL);
  }
  
  typedef struct http_file {
  	char *path;
  	char *ctype;
  } http_file;
  
  static void
  http_handle_file(nni_aio *aio)
  {
  	nni_http_handler *h   = nni_aio_get_input(aio, 1);
  	nni_http_res *    res = NULL;
  	void *            data;
  	size_t            size;
  	int               rv;
  	http_file *       hf = nni_http_handler_get_data(h);
  	const char *      ctype;
  
  	if ((ctype = hf->ctype) == NULL) {
  		ctype = "application/octet-stream";
  	}
  
  	// 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(hf->path, &data, &size)) != 0) {
  		uint16_t status;
  		switch (rv) {
  		case NNG_ENOMEM:
  			status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR;
  			break;
  		case NNG_ENOENT:
  			status = NNG_HTTP_STATUS_NOT_FOUND;
  			break;
  		case NNG_EPERM:
  			status = NNG_HTTP_STATUS_FORBIDDEN;
  			break;
  		default:
  			status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR;
  			break;
  		}
  		if ((rv = nni_http_res_alloc_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_alloc(&res)) != 0) ||
  	    ((rv = nni_http_res_set_status(res, NNG_HTTP_STATUS_OK)) != 0) ||
  	    ((rv = nni_http_res_set_header(res, "Content-Type", ctype)) !=
  	        0) ||
  	    ((rv = nni_http_res_copy_data(res, data, size)) != 0)) {
  		nni_http_res_free(res);
  		nni_free(data, size);
  		nni_aio_finish_error(aio, rv);
  		return;
  	}
  
  	nni_free(data, size);
  	nni_aio_set_output(aio, 0, res);
  	nni_aio_finish(aio, 0, 0);
  }
  
  static void
  http_file_free(void *arg)
  {
  	http_file *hf;
  	if ((hf = arg) != NULL) {
  		nni_strfree(hf->path);
  		nni_strfree(hf->ctype);
  		NNI_FREE_STRUCT(hf);
  	}
  }
  
  int
  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 *       hf;
  	int               rv;
  
  	if ((hf = NNI_ALLOC_STRUCT(hf)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  
  	// 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 (((hf->path = nni_strdup(path)) == NULL) ||
  	    ((hf->ctype = nni_strdup(ctype)) == NULL)) {
  		http_file_free(hf);
  		return (NNG_ENOMEM);
  	}
  
  	if ((rv = nni_http_handler_init(&h, uri, http_handle_file)) != 0) {
  		http_file_free(hf);
  		return (rv);
  	}
  
  	if ((rv = nni_http_handler_set_data(h, hf, http_file_free)) != 0) {
  		http_file_free(hf);
  		nni_http_handler_fini(h);
  		return (rv);
  	}
  
  	// We don't permit a body for getting a file.
  	nni_http_handler_collect_body(h, true, 0);
  
  	*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_handle_dir(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;
  	http_file *       hf   = nni_http_handler_get_data(h);
  	const char *      path = hf->path;
  	const char *      base = nni_http_handler_get_uri(h); // 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 (base[1] != '\0' && // Allows "/" as base
  	    ((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 separator
  	// It's never actually that bad, because we we have /<something>/.
  	pnsz = (strlen(path) + strlen(uri) + 2) * strlen(NNG_PLATFORM_DIR_SEP);
  	pnsz += strlen("index.html") + 1; // +1 for term nul
  
  	if ((pn = nni_alloc(pnsz)) == NULL) {
  		nni_aio_finish_error(aio, NNG_ENOMEM);
  		return;
  	}
  
  	// make sure we have a "/" present.
  	strcpy(pn, path);
  	dst = pn + strlen(pn);
  
  	if ((dst == pn) || (dst[-1] != '/')) {
  		*dst++ = '/';
  	}
  
  	for (uri = uri + len; *uri != '\0'; uri++) {
  		if (*uri == '/') {
  			strcpy(dst, NNG_PLATFORM_DIR_SEP);
  			dst += sizeof(NNG_PLATFORM_DIR_SEP) - 1;
  		} else {
  			*dst++ = *uri;
  		}
  	}
  
  	*dst = '\0';
  
  	// This is a very simplistic file server, suitable only for small
  	// files.  In the future we can use an AIO based file read, where
  	// we read files a bit at a time, or even mmap them, and serve
  	// them up chunkwise.  Applications could even come up with their
  	// own caching version of the http handler.
  
  	rv = 0;
  	if (nni_file_is_dir(pn)) {
  		sprintf(dst, "%s%s", NNG_PLATFORM_DIR_SEP, "index.html");
  		if (!nni_file_is_file(pn)) {
  			pn[strlen(pn) - 1] = '\0'; // index.html -> index.htm
  			if (!nni_file_is_file(pn)) {
  				rv = NNG_ENOENT;
  			}
  		}
  	}
  
  	if (rv == 0) {
  		rv = nni_file_get(pn, &data, &size);
  	} else {
  		data = NULL;
  		size = 0;
  	}
  	ctype = http_lookup_type(pn);
  	if (ctype == NULL) {
  		ctype = "application/octet-stream";
  	}
  
  	nni_free(pn, pnsz);
  	if (rv != 0) {
  		uint16_t status;
  
  		switch (rv) {
  		case NNG_ENOMEM:
  			status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR;
  			break;
  		case NNG_ENOENT:
  			status = NNG_HTTP_STATUS_NOT_FOUND;
  			break;
  		case NNG_EPERM:
  			status = NNG_HTTP_STATUS_FORBIDDEN;
  			break;
  		default:
  			status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR;
  			break;
  		}
  		if ((rv = nni_http_res_alloc_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_alloc(&res)) != 0) ||
  	    ((rv = nni_http_res_set_status(res, NNG_HTTP_STATUS_OK)) != 0) ||
  	    ((rv = nni_http_res_set_header(res, "Content-Type", ctype)) !=
  	        0) ||
  	    ((rv = nni_http_res_copy_data(res, data, size)) != 0)) {
  		nni_http_res_free(res);
  		nni_free(data, size);
  		nni_aio_finish_error(aio, rv);
  		return;
  	}
  
  	nni_free(data, size);
  	nni_aio_set_output(aio, 0, res);
  	nni_aio_finish(aio, 0, 0);
  }
  
  int
  nni_http_handler_init_directory(
      nni_http_handler **hpp, const char *uri, const char *path)
  {
  	http_file *       hf;
  	nni_http_handler *h;
  	int               rv;
  
  	if ((hf = NNI_ALLOC_STRUCT(hf)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	if ((hf->path = nni_strdup(path)) == NULL) {
  		NNI_FREE_STRUCT(hf);
  		return (NNG_ENOMEM);
  	}
  
  	if ((rv = nni_http_handler_init(&h, uri, http_handle_dir)) != 0) {
  		http_file_free(hf);
  		return (rv);
  	}
  	// We don't permit a body for getting a file.
  	nni_http_handler_collect_body(h, true, 0);
  
  	if (((rv = nni_http_handler_set_tree_exclusive(h)) != 0) ||
  	    ((rv = nni_http_handler_set_data(h, hf, http_file_free)) != 0)) {
  		http_file_free(hf);
  		nni_http_handler_fini(h);
  		return (rv);
  	}
  
  	*hpp = h;
  	return (0);
  }
  
  typedef struct http_redirect {
  	uint16_t code;
  	char *   where;
  } http_redirect;
  
  static void
  http_handle_redirect(nni_aio *aio)
  {
  	nni_http_res *    r    = NULL;
  	char *            html = NULL;
  	char *            msg  = NULL;
  	char *            loc  = NULL;
  	http_redirect *   hr;
  	nni_http_handler *h;
  	int               rv;
  	nni_http_req *    req;
  	const char *      base;
  	const char *      uri;
  
  	req  = nni_aio_get_input(aio, 0);
  	h    = nni_aio_get_input(aio, 1);
  	base = nni_http_handler_get_uri(h); // base uri
  	uri  = nni_http_req_get_uri(req);
  
  	hr = nni_http_handler_get_data(h);
  
  	// If we are doing a full tree, then include the entire suffix.
  	if (strncmp(uri, base, strlen(base)) == 0) {
  		rv = nni_asprintf(&loc, "%s%s", hr->where, uri + strlen(base));
  		if (rv != 0) {
  			nni_aio_finish_error(aio, rv);
  			return;
  		}
  	} else {
  		loc = hr->where;
  	}
  
  	// Builtin redirect page
  	rv = nni_asprintf(&msg,
  	    "You should be automatically redirected to <a href=\"%s\">%s</a>.",
  	    loc, loc);
  
  	// Build a response.  We always close the connection for redirects,
  	// because it is probably going to another server.  This also
  	// keeps us from having to consume the entity body, we can just
  	// discard it.
  	if ((rv != 0) || ((rv = nni_http_res_alloc(&r)) != 0) ||
  	    ((rv = nni_http_alloc_html_error(&html, hr->code, msg)) != 0) ||
  	    ((rv = nni_http_res_set_status(r, hr->code)) != 0) ||
  	    ((rv = nni_http_res_set_header(r, "Connection", "close")) != 0) ||
  	    ((rv = nni_http_res_set_header(
  	          r, "Content-Type", "text/html; charset=UTF-8")) != 0) ||
  	    ((rv = nni_http_res_set_header(r, "Location", loc)) != 0) ||
  	    ((rv = nni_http_res_copy_data(r, html, strlen(html))) != 0)) {
  		if (loc != hr->where) {
  			nni_strfree(loc);
  		}
  		nni_strfree(msg);
  		nni_strfree(html);
  		nni_http_res_free(r);
  		nni_aio_finish_error(aio, rv);
  		return;
  	}
  	if (loc != hr->where) {
  		nni_strfree(loc);
  	}
  	nni_strfree(msg);
  	nni_strfree(html);
  	nni_aio_set_output(aio, 0, r);
  	nni_aio_finish(aio, 0, 0);
  }
  
  static void
  http_redirect_free(void *arg)
  {
  	http_redirect *hr;
  
  	if ((hr = arg) != NULL) {
  		nni_strfree(hr->where);
  		NNI_FREE_STRUCT(hr);
  	}
  }
  
  int
  nni_http_handler_init_redirect(nni_http_handler **hpp, const char *uri,
      uint16_t status, const char *where)
  {
  	nni_http_handler *h;
  	int               rv;
  	http_redirect *   hr;
  
  	if ((hr = NNI_ALLOC_STRUCT(hr)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	if ((hr->where = nni_strdup(where)) == NULL) {
  		NNI_FREE_STRUCT(hr);
  		return (NNG_ENOMEM);
  	}
  	if (status == 0) {
  		status = NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY;
  	}
  	hr->code = status;
  
  	if ((rv = nni_http_handler_init(&h, uri, http_handle_redirect)) != 0) {
  		http_redirect_free(hr);
  		return (rv);
  	}
  
  	if (((rv = nni_http_handler_set_method(h, NULL)) != 0) ||
  	    ((rv = nni_http_handler_set_data(h, hr, http_redirect_free)) !=
  	        0)) {
  		http_redirect_free(hr);
  		nni_http_handler_fini(h);
  		return (rv);
  	}
  
  	// We don't need to collect the body at all, because the handler
  	// just discards the content and closes the connection.
  	nni_http_handler_collect_body(h, false, 0);
  
  	*hpp = h;
  	return (0);
  }
  
  typedef struct http_static {
  	void * data;
  	size_t size;
  	char * ctype;
  } http_static;
  
  static void
  http_handle_static(nni_aio *aio)
  {
  	http_static *     hs;
  	const char *      ctype;
  	nni_http_handler *h;
  	nni_http_res *    r = NULL;
  	int               rv;
  
  	h  = nni_aio_get_input(aio, 1);
  	hs = nni_http_handler_get_data(h);
  
  	if ((ctype = hs->ctype) == NULL) {
  		ctype = "application/octet-stream";
  	}
  
  	if (((rv = nni_http_res_alloc(&r)) != 0) ||
  	    ((rv = nni_http_res_set_header(r, "Content-Type", ctype)) != 0) ||
  	    ((rv = nni_http_res_set_status(r, NNG_HTTP_STATUS_OK)) != 0) ||
  	    ((rv = nni_http_res_set_data(r, hs->data, hs->size)) != 0)) {
  		nni_http_res_free(r);
  		nni_aio_finish_error(aio, rv);
  		return;
  	}
  
  	nni_aio_set_output(aio, 0, r);
  	nni_aio_finish(aio, 0, 0);
  }
  
  static void
  http_static_free(void *arg)
  {
  	http_static *hs;
  
  	if ((hs = arg) != NULL) {
  		nni_free(hs->data, hs->size);
  		nni_strfree(hs->ctype);
  		NNI_FREE_STRUCT(hs);
  	}
  }
  
  int
  nni_http_handler_init_static(nni_http_handler **hpp, const char *uri,
      const void *data, size_t size, const char *ctype)
  {
  	nni_http_handler *h;
  	int               rv;
  	http_static *     hs;
  
  	if ((hs = NNI_ALLOC_STRUCT(hs)) == NULL) {
  		return (NNG_ENOMEM);
  	}
  	if (((hs->ctype = nni_strdup(ctype)) == NULL) ||
  	    ((size > 0) && ((hs->data = nni_alloc(size)) == NULL))) {
  		http_static_free(hs);
  		return (NNG_ENOMEM);
  	}
  	hs->size = size;
  	memcpy(hs->data, data, size);
  
  	if ((rv = nni_http_handler_init(&h, uri, http_handle_static)) != 0) {
  		http_static_free(hs);
  		return (rv);
  	}
  
  	if ((rv = nni_http_handler_set_data(h, hs, http_static_free)) != 0) {
  		http_static_free(hs);
  		nni_http_handler_fini(h);
  		return (rv);
  	}
  
  	// We don't permit a body for getting static data.
  	nni_http_handler_collect_body(h, true, 0);
  
  	*hpp = h;
  	return (0);
  }
  
  int
  nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tls)
  {
  	return (
  	    nng_stream_listener_set_ptr(s->listener, NNG_OPT_TLS_CONFIG, tls));
  }
  
  int
  nni_http_server_get_tls(nni_http_server *s, nng_tls_config **tlsp)
  {
  	return (nng_stream_listener_get_ptr(
  	    s->listener, NNG_OPT_TLS_CONFIG, (void **) tlsp));
  }
  
  int
  nni_http_server_set(nni_http_server *s, const char *name, const void *buf,
      size_t sz, nni_type t)
  {
  	// We have no local options, but we just pass them straight through.
  	return (nni_stream_listener_set(s->listener, name, buf, sz, t));
  }
  
  int
  nni_http_server_get(
      nni_http_server *s, const char *name, void *buf, size_t *szp, nni_type t)
  {
  	return (nni_stream_listener_get(s->listener, name, buf, szp, t));
  }
  
  void
  nni_http_server_fini(nni_http_server *s)
  {
  	nni_mtx_lock(&http_servers_lk);
  	s->refcnt--;
  	if (s->refcnt == 0) {
  		nni_mtx_lock(&s->mtx);
  		http_server_stop(s);
  		nni_mtx_unlock(&s->mtx);
  		nni_list_remove(&http_servers, s);
  		nni_reap(&http_server_reap_list, s);
  	}
  	nni_mtx_unlock(&http_servers_lk);
  }
  