qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From acon...@apache.org
Subject [3/3] qpid-dispatch git commit: DISPATCH-103: SSL support for HTTP/WebSocket listeners
Date Tue, 20 Dec 2016 17:05:40 GMT
DISPATCH-103: SSL support for HTTP/WebSocket listeners

Re-worked HTTP implementation: stability, performance and maintainability improvements.
- Use single lws_context per dispatch router.
- Use separate lws_vhost per dispatch listener for independent SSL configuration.
- Include libwebsocket messages in dispatch logs (module HTTP)
- Move HTTP code out of driver and into server (not POSIX-specific)
- Fix some memory and connection close bugs

Apply dispatch SSL listener configuration to libwebsocket vhosts.
Tests to verify HTTP access working correctly.

The console does not yet work over HTTPs, there are some console issues to fix
for that to work.


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/9442bb0b
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/9442bb0b
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/9442bb0b

Branch: refs/heads/master
Commit: 9442bb0b231d008d604db565254d8d6014070516
Parents: 6ecbdba
Author: Alan Conway <aconway@redhat.com>
Authored: Wed Dec 7 17:28:01 2016 -0500
Committer: Alan Conway <aconway@redhat.com>
Committed: Tue Dec 20 12:03:56 2016 -0500

----------------------------------------------------------------------
 CMakeLists.txt                                  |   2 +-
 cmake/FindLibWebSockets.cmake                   |  13 +
 .../dashboard/dispatch/dispatch.comService.js   |   4 +-
 include/qpid/dispatch/driver.h                  |  30 +-
 include/qpid/dispatch/server.h                  |   5 +
 python/qpid_dispatch/management/qdrouter.json   |  20 +-
 src/connection_manager.c                        |   3 +
 src/http-libwebsockets.c                        | 366 ++++++++++++-------
 src/http.h                                      |  21 +-
 src/posix/driver.c                              |  53 ++-
 src/server.c                                    |  39 +-
 src/server_private.h                            |   3 +-
 tests/CMakeLists.txt                            |   8 +-
 tests/system_tests_http.py                      | 120 ++++++
 tests/system_tests_http.txt                     |   1 +
 15 files changed, 482 insertions(+), 206 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 51bbc17..f50a3dc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -100,7 +100,7 @@ endif (NOT COMMAND add_compile_options)
 find_library(pthread_lib pthread)
 find_library(dl_lib dl)
 find_library(rt_lib rt)
-find_package(Proton 0.13 REQUIRED)
+find_package(Proton 0.15 REQUIRED)
 
 ## Optional dependencies
 include(FindLibWebSockets)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/cmake/FindLibWebSockets.cmake
----------------------------------------------------------------------
diff --git a/cmake/FindLibWebSockets.cmake b/cmake/FindLibWebSockets.cmake
index 2e6bb9b..132c903 100644
--- a/cmake/FindLibWebSockets.cmake
+++ b/cmake/FindLibWebSockets.cmake
@@ -43,8 +43,21 @@ find_path(LibWebSockets_INCLUDE_DIRS
   )
 
 include(FindPackageHandleStandardArgs)
+
 find_package_handle_standard_args(LibWebSockets DEFAULT_MSG LibWebSockets_LIBRARIES LibWebSockets_INCLUDE_DIRS)
 
+if(LibWebSockets_FOUND)
+  # For the moment we need a patched version of LibWebSockets:
+  # https://github.com/alanconway/libwebsockets/tree/v2.1-stable-aconway-adopt-ssl
+  # This function check verifies we have it.
+  set(CMAKE_REQUIRED_INCLUDES ${LibWebSockets_INCLUDE_DIRS})
+  set(CMAKE_REQUIRED_LIBRARIES ${LibWebSockets_LIBRARIES})
+  check_function_exists(lws_adopt_socket_vhost LWS_ADOPT_SOCKET_VHOST_FOUND)
+  if (NOT LWS_ADOPT_SOCKET_VHOST_FOUND)
+    unset(LibWebSockets_FOUND)
+  endif()
+endif()
+
 if(NOT LibWebSockets_FOUND)
   set(LibWebSockets_LIBRARIES "")
   set(LibWebSockets_INCLUDE_DIRS "")

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
index 41814e4..bbc267e 100644
--- a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
@@ -875,9 +875,7 @@ var QDR = (function(QDR) {
 			try {
 QDR.log.debug("trying to connect to ws://" + baseAddress)
                 connection = self.rhea.connect({
-                    // FIXME aconway 2016-11-29: "binary" for wsproxy,
-                    // should also include "amqp" - waiting on libwebsocket fix.
-                    connection_details:ws('ws://' + baseAddress, ["binary"]),
+                    connection_details:ws('ws://' + baseAddress, ["amqp", "binary", "AMQWSB10"]),
                     reconnect:true,
                     properties: {console_identifier: 'Dispatch console'}
 	            });

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/include/qpid/dispatch/driver.h
----------------------------------------------------------------------
diff --git a/include/qpid/dispatch/driver.h b/include/qpid/dispatch/driver.h
index f08d54a..6c24a23 100644
--- a/include/qpid/dispatch/driver.h
+++ b/include/qpid/dispatch/driver.h
@@ -31,9 +31,6 @@
 #include <proton/transport.h>
 #include <proton/types.h>
 
-struct qd_http_t;
-struct qd_http_connector_t;
-
 /** @file
  * API for the Driver Layer.
  *
@@ -141,19 +138,17 @@ void qdpn_driver_free(qdpn_driver_t *driver);
  * @param[in] host local host address to listen on
  * @param[in] port local port to listen on
  * @param[in] protocol family to use (IPv4 or IPv6 or 0). If 0 (zero) is passed in the protocol family will be automatically determined from the address
- * @param[in] http points to qd_http_t if HTTP is enabled.
  * @param[in] context application-supplied, can be accessed via
  *                    qdpn_listener_context()
+ * @param[in] methods to apply to new connectors.
  * @return a new listener on the given host:port, NULL if error
  */
 qdpn_listener_t *qdpn_listener(qdpn_driver_t *driver,
                                const char *host,
                                const char *port,
                                const char *protocol_family,
-                               struct qd_http_t  *http,
-                               void* context);
-
-struct qd_http_t *qdpn_listener_http(qdpn_listener_t *l);
+                               void* context
+                              );
 
 /** Access the head listener for a driver.
  *
@@ -398,17 +393,18 @@ void qdpn_activate_all(qdpn_driver_t *driver);
  */
 bool qdpn_connector_activated(qdpn_connector_t *connector, qdpn_activate_criteria_t criteria);
 
+/** True if the connector has received a hangup */
+bool qdpn_connector_hangup(qdpn_connector_t *connector);
+
 /** Create a listener using the existing file descriptor.
  *
  * @param[in] driver driver that will 'own' this listener
  * @param[in] fd existing socket for listener to listen on
- * @param[in] http if non-NULL enable as a HTTP listener
  * @param[in] context application-supplied, can be accessed via
  *                    qdpn_listener_context()
  * @return a new listener on the given host:port, NULL if error
  */
-qdpn_listener_t *qdpn_listener_fd(qdpn_driver_t *driver, pn_socket_t fd,
-                                  struct qd_http_t *http, void *context);
+qdpn_listener_t *qdpn_listener_fd(qdpn_driver_t *driver, pn_socket_t fd, void *context);
 
 pn_socket_t qdpn_listener_get_fd(qdpn_listener_t *listener);
 
@@ -425,15 +421,21 @@ qdpn_connector_t *qdpn_connector_fd(qdpn_driver_t *driver, pn_socket_t fd, void
 /** Get the file descriptor for this connector */
 int qdpn_connector_get_fd(qdpn_connector_t *connector);
 
-/** Get the HTTP per-connector state for this connector, NULL if not enabled. */
-struct qd_http_connector_t *qdpn_connector_http(qdpn_connector_t* c);
-
 /** Set the wakeup time on the connector */
 void qdpn_connector_wakeup(qdpn_connector_t* c, pn_timestamp_t t);
 
 /** Current time according */
 pn_timestamp_t qdpn_now();
 
+/** Implementation of connector methods (e.g. these are different for HTTP connectors */
+typedef struct qdpn_connector_methods_t {
+    void (*process)(qdpn_connector_t *c);
+    void (*close)(qdpn_connector_t *c);
+} qdpn_connector_methods_t;
+
+/** Set new methods for a connector (e.g. because it is a HTTP connector) */
+void qdpn_connector_set_methods(qdpn_connector_t *c, qdpn_connector_methods_t *methods);
+
 /**@}*/
 
 #endif /* driver.h */

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/include/qpid/dispatch/server.h
----------------------------------------------------------------------
diff --git a/include/qpid/dispatch/server.h b/include/qpid/dispatch/server.h
index 0ca2bdd..b28f1cc 100644
--- a/include/qpid/dispatch/server.h
+++ b/include/qpid/dispatch/server.h
@@ -259,6 +259,11 @@ typedef struct qd_server_config_t {
     bool http;
 
     /**
+     * Directory for HTTP content
+     */
+    char *http_root;
+
+    /**
      * Connection name, used as a reference from other parts of the configuration.
      */
     char *name;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index 584b9c4..0edec2f 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -543,7 +543,7 @@
                 },
                 "displayNameFile": {
                     "type": "string",
-                    "description": "The absolute path to the file containing the unique id to dispay name mapping",
+                    "description": "The absolute path to the file containing the unique id to display name mapping",
                     "create": true
                 }
             }
@@ -573,12 +573,6 @@
                     "description": "['IPv4', 'IPv6'] IPv4: Internet Protocol version 4; IPv6: Internet Protocol version 6.  If not specified, the protocol family will be automatically determined from the address.",
                     "create": true
                 },
-                "http": {
-                    "type": "boolean",
-                    "default": false,
-                    "description": "Accept HTTP connections that can upgrade to AMQP over WebSocket",
-                    "create": true
-                },
                 "role": {
                     "type": [
                         "normal",
@@ -700,6 +694,17 @@
                     "create": true,
                     "deprecated": true,
                     "description": "(DEPRECATED) This attribute is now controlled by the requireEncryption attribute."
+                },
+                "http": {
+                    "type": "boolean",
+                    "default": false,
+                    "description": "Accept HTTP connections that can upgrade to AMQP over WebSocket",
+                    "create": true
+                },
+                "httpRoot": {
+                    "type": "path",
+                    "description": "Serve HTTP files from this directory, defaults to the installed stand-alone console directory",
+                    "create": true
                 }
             }
         },
@@ -849,6 +854,7 @@
                         "CONTAINER",
                         "ERROR",
                         "POLICY",
+                        "HTTP",
                         "DEFAULT"
                     ],
                     "required": true,

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/src/connection_manager.c
----------------------------------------------------------------------
diff --git a/src/connection_manager.c b/src/connection_manager.c
index 606f731..dabeea1 100644
--- a/src/connection_manager.c
+++ b/src/connection_manager.c
@@ -99,6 +99,7 @@ static void qd_server_config_free(qd_server_config_t *cf)
     free(cf->host);
     free(cf->port);
     free(cf->role);
+    if (cf->http_root)       free(cf->http_root);
     if (cf->name)            free(cf->name);
     if (cf->protocol_family) free(cf->protocol_family);
     if (cf->sasl_username)   free(cf->sasl_username);
@@ -242,6 +243,8 @@ static qd_error_t load_server_config(qd_dispatch_t *qd, qd_server_config_t *conf
     config->inter_router_cost    = qd_entity_opt_long(entity, "cost", 1);             CHECK();
     config->protocol_family      = qd_entity_opt_string(entity, "protocolFamily", 0); CHECK();
     config->http                 = qd_entity_opt_bool(entity, "http", false);         CHECK();
+    config->http_root            = qd_entity_opt_string(entity, "httpRoot", false);   CHECK();
+    config->http = config->http || config->http_root; /* httpRoot implies http */
     config->max_frame_size       = qd_entity_get_long(entity, "maxFrameSize");        CHECK();
     config->max_sessions         = qd_entity_get_long(entity, "maxSessions");         CHECK();
     uint64_t ssn_frames          = qd_entity_get_long(entity, "maxSessionFrames");    CHECK();

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/src/http-libwebsockets.c
----------------------------------------------------------------------
diff --git a/src/http-libwebsockets.c b/src/http-libwebsockets.c
index c456b07..798851a 100644
--- a/src/http-libwebsockets.c
+++ b/src/http-libwebsockets.c
@@ -20,7 +20,6 @@
 #include <qpid/dispatch/atomic.h>
 #include <qpid/dispatch/amqp.h>
 #include <qpid/dispatch/driver.h>
-#include <qpid/dispatch/log.h>
 #include <qpid/dispatch/threading.h>
 #include <qpid/dispatch/timer.h>
 
@@ -28,55 +27,70 @@
 
 #include <assert.h>
 #include <errno.h>
+#include <inttypes.h>
 
 #include "http.h"
+#include "server_private.h"
 #include "config.h"
 
-/* Shared context for all HTTP connections.  */
-struct qd_http_t {
-    sys_mutex_t *lock;
+static qd_log_source_t* http_log;
+
+static const char *CIPHER_LIST = "ALL:aNULL:!eNULL:@STRENGTH";
+
+/* Associate file-descriptors, LWS instances and qdpn_connectors */
+typedef struct fd_data_t {
+    qdpn_connector_t *connector;
+    struct lws *wsi;
+} fd_data_t;
+
+/* HTTP server state shared by all listeners  */
+struct qd_http_server_t {
     qd_dispatch_t *dispatch;
     qd_log_source_t *log;
+    sys_mutex_t *lock;         /* For now use LWS as a thread-unsafe library. */
     struct lws_context *context;
     qd_timer_t *timer;
-    qdpn_connector_t **connectors; /* Indexed by file descriptor */
-    size_t connectors_len;
+    int vhost_id;               /* unique identifier for vhost name */
+    fd_data_t *fd;              /* indexed by file descriptor */
+    size_t fd_len;
 };
 
-static inline qdpn_connector_t *fd_connector(qd_http_t *h, int fd) {
-    return (fd < h->connectors_len) ? h->connectors[fd] : NULL;
-}
+/* Per-HTTP-listener */
+struct qd_http_listener_t {
+    qd_http_server_t *server;
+    struct lws_vhost *vhost;
+    struct lws_http_mount mount;
+    char name[256];             /* vhost name */
+};
 
-static inline qd_http_t *wsi_http(struct lws *wsi) {
-    return (qd_http_t *)lws_context_user(lws_get_context(wsi));
+/* Get wsi/connector associated with fd or NULL if nothing on record. */
+static inline fd_data_t *fd_data(qd_http_server_t *s, int fd) {
+    fd_data_t *d = (fd < s->fd_len) ? &s->fd[fd] : NULL;
+    return (d && (d->connector || d->wsi)) ? d : NULL;
 }
 
-static inline qdpn_connector_t *wsi_connector(struct lws *wsi) {
-    return fd_connector(wsi_http(wsi), lws_get_socket_fd(wsi));
+static inline qd_http_server_t *wsi_http_server(struct lws *wsi) {
+    return (qd_http_server_t*)lws_context_user(lws_get_context(wsi));
 }
 
-static inline int set_fd(qd_http_t *h, int fd, qdpn_connector_t *c) {
-    if (fd >= h->connectors_len) {
-        size_t len = h->connectors_len;
-        h->connectors_len = (fd+1)*2;
-        h->connectors = realloc(h->connectors, h->connectors_len*sizeof(qdpn_connector_t*));
-        if (!h->connectors) return -1;
-        memset(h->connectors + len, 0, h->connectors_len - len);
-    }
-    h->connectors[fd] = c;
-    return 0;
+static inline qdpn_connector_t *wsi_connector(struct lws *wsi) {
+    fd_data_t *d = fd_data(wsi_http_server(wsi), lws_get_socket_fd(wsi));
+    return d ? d->connector : NULL;
 }
 
-/* Mark the qd connector closed, but leave the FD for LWS to clean up */
-int mark_closed(struct lws *wsi) {
-    qd_http_t *h = wsi_http(wsi);
-    int fd = lws_get_socket_fd(wsi);
-    qdpn_connector_t *c = fd_connector(h, fd);
-    if (c) {
-        qdpn_connector_mark_closed(c);
-        return set_fd(h, fd, NULL);
+static inline fd_data_t *set_fd(qd_http_server_t *s, int fd, qdpn_connector_t *c, struct lws *wsi) {
+    if (!s->fd || fd >= s->fd_len) {
+        size_t oldlen = s->fd_len;
+        s->fd_len = fd + 16;    /* Don't double, low-range FDs will be re-used first. */
+        void *newfds = realloc(s->fd, s->fd_len*sizeof(*s->fd));
+        if (!newfds) return NULL;
+        s->fd = newfds;
+        memset(s->fd + oldlen, 0, sizeof(*s->fd)*(s->fd_len - oldlen));
     }
-    return 0;
+    fd_data_t *d = &s->fd[fd];
+    d->connector = c;
+    d->wsi = wsi;
+    return d;
 }
 
 /* Push read data into the transport.
@@ -97,12 +111,12 @@ static int transport_push(pn_transport_t *t, pn_bytes_t buf) {
     return buf.size;
 }
 
-static int normal_close(struct lws *wsi, qdpn_connector_t *c, const char *msg) {
+static inline int normal_close(struct lws *wsi, const char *msg) {
     lws_close_reason(wsi, LWS_CLOSE_STATUS_NORMAL, (unsigned char*)msg, strlen(msg));
     return -1;
 }
 
-static int unexpected_close(struct lws *wsi, qdpn_connector_t *c, const char *msg) {
+static inline int unexpected_close(struct lws *wsi, const char *msg) {
     lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, (unsigned char*)msg, strlen(msg));
     return -1;
 }
@@ -117,24 +131,24 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
 {
     switch (reason) {
 
-    case LWS_CALLBACK_HTTP: {   /* Called if file mount can't find the file */
-        lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, "file not found");
+    case LWS_CALLBACK_HTTP:     /* Called if file mount can't find the file */
+        lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, (char*)in);
         return -1;
-    }
 
-    case LWS_CALLBACK_CLOSED_HTTP:
-        mark_closed(wsi);
+    case LWS_CALLBACK_ADD_POLL_FD: {
+        /* Record WSI against FD here, the connector will be recorded when lws_service returns. */
+        set_fd(wsi_http_server(wsi), lws_get_socket_fd(wsi), 0, wsi);
         break;
-
-        /* low-level 'protocol[0]' callbacks for all protocols   */
+    }
     case LWS_CALLBACK_DEL_POLL_FD: {
-        if (mark_closed(wsi)) {
-            lws_return_http_status(wsi, HTTP_STATUS_INTERNAL_SERVER_ERROR, "out of memory");
-            return -1;
+        fd_data_t *d = fd_data(wsi_http_server(wsi), lws_get_socket_fd(wsi));
+        if (d) {
+            /* Tell dispatch to forget this FD, but let LWS do the actual close() */
+            if (d->connector) qdpn_connector_mark_closed(d->connector);
+            memset(d, 0, sizeof(*d));
         }
         break;
     }
-
     case LWS_CALLBACK_CHANGE_MODE_POLL_FD: {
         struct lws_pollargs *p = (struct lws_pollargs*)in;
         qdpn_connector_t *c = wsi_connector(wsi);
@@ -145,6 +159,8 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
         break;
     }
 
+    /* NOTE: Not using LWS_CALLBACK_LOCK/UNLOCK_POLL as we are serializing all HTTP work for now. */
+
     default:
         break;
     }
@@ -161,23 +177,24 @@ typedef struct buffer_t { void *start; size_t size; size_t cap; } buffer_t;
 static int callback_amqpws(struct lws *wsi, enum lws_callback_reasons reason,
                            void *user, void *in, size_t len)
 {
-    qd_http_t *h = wsi_http(wsi);
     qdpn_connector_t *c = wsi_connector(wsi);
     pn_transport_t *t = c ? qdpn_connector_transport(c) : NULL;
-    const char *name = c ? qdpn_connector_name(c) : "<unknown>";
 
     switch (reason) {
 
     case LWS_CALLBACK_ESTABLISHED: {
+        qd_log(wsi_http_server(wsi)->log, QD_LOG_DEBUG,
+               "Upgraded incoming HTTP connection from  %s[%"PRIu64"] to AMQP over WebSocket",
+               qdpn_connector_name(c),
+               qd_connection_connection_id((qd_connection_t*)qdpn_connector_context(c)));
         memset(user, 0, sizeof(buffer_t));
-        qd_log(h->log, QD_LOG_TRACE, "HTTP from %s upgraded to AMQP/WebSocket", name);
         break;
     }
 
     case LWS_CALLBACK_SERVER_WRITEABLE: {
         ssize_t size;
         if (!t || (size = pn_transport_pending(t)) < 0) {
-            return normal_close(wsi, c, "write-closed");
+            return normal_close(wsi, "write-closed");
         }
         if (size > 0) {
             const void *start = pn_transport_head(t);
@@ -189,14 +206,14 @@ static int callback_amqpws(struct lws *wsi, enum lws_callback_reasons reason,
                 wtmp->size = wtmp->cap = tmpsize;
             }
             if (wtmp->start == NULL) {
-                return unexpected_close(wsi, c, "out-of-memory");
+                return unexpected_close(wsi, "out-of-memory");
             }
             void *tmpstart = wtmp->start + LWS_PRE;
             memcpy(tmpstart, start, size);
             ssize_t wrote = lws_write(wsi, tmpstart, size, LWS_WRITE_BINARY);
             if (wrote < 0) {
                 pn_transport_close_head(t);
-                return normal_close(wsi, c, "write-error");
+                return normal_close(wsi, "write-error");
             } else {
                 pn_transport_pop(t, (size_t)wrote);
             }
@@ -206,22 +223,20 @@ static int callback_amqpws(struct lws *wsi, enum lws_callback_reasons reason,
 
     case LWS_CALLBACK_RECEIVE: {
         if (!t || pn_transport_capacity(t) < 0) {
-            return normal_close(wsi, c, "read-closed");
+            return normal_close(wsi, "read-closed");
         }
         if (transport_push(t, pn_bytes(len, in))) {
-            return unexpected_close(wsi, c, "read-overflow");
+            return unexpected_close(wsi, "read-overflow");
         }
         break;
     }
 
     case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
-        mark_closed(wsi);
         if (t) {
             pn_transport_close_tail(t);
         }
 
     case LWS_CALLBACK_CLOSED:
-        mark_closed(wsi);
         break;
 
     default:
@@ -230,79 +245,96 @@ static int callback_amqpws(struct lws *wsi, enum lws_callback_reasons reason,
     return 0;
 }
 
-/* Mount the console directory into URL space at /  */
-static const struct lws_http_mount console_mount = {
-    NULL,		/* linked-list pointer to next*/
-    "/",		/* mountpoint in URL namespace on this vhost */
-    QPID_CONSOLE_STAND_ALONE_INSTALL_DIR, /* where to go on the filesystem for that */
-    "index.html",        /* default filename if none given */
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    0,
-    0,
-    0,
-    0,
-    0,
-    0,
-    LWSMPRO_FILE,	/* mount type is a directory in a filesystem */
-    1,                  /* strlen("/"), ie length of the mountpoint */
-};
-
-static void check_timer(void *void_http) {
-    qd_http_t *h = (qd_http_t*)void_http;
-    sys_mutex_lock(h->lock);
+static void check_timer(void *void_http_server) {
+    qd_http_server_t *s = (qd_http_server_t*)void_http_server;
     /* Run LWS global timer and forced-service checks. */
-    lws_service_fd(h->context, NULL);
-    while (!lws_service_adjust_timeout(h->context, 1, 0)) {
+    sys_mutex_lock(s->lock);
+    lws_service_fd(s->context, NULL);
+    while (!lws_service_adjust_timeout(s->context, 1, 0)) {
         /* -1 timeout means just do forced service */
-        lws_plat_service_tsi(h->context, -1, 0);
+        lws_plat_service_tsi(s->context, -1, 0);
     }
-    if (!h->timer) {
-        h->timer = qd_timer(h->dispatch, check_timer, h);
+    if (!s->timer) {
+        s->timer = qd_timer(s->dispatch, check_timer, s);
     }
-    qd_timer_cancel(h->timer);
-    qd_timer_schedule(h->timer, 1000); /* LWS wants per-second wakeups */
-    sys_mutex_unlock(h->lock);
+    sys_mutex_unlock(s->lock);
+    /* Timer is locked using server lock. */
+    qd_timer_cancel(s->timer);
+    qd_timer_schedule(s->timer, 1000); /* LWS wants per-second wakeups */
+}
+
+static qd_http_listener_t * qdpn_connector_http_listener(qdpn_connector_t* c) {
+    qd_listener_t* ql = (qd_listener_t*)qdpn_listener_context(qdpn_connector_listener(c));
+    return ql->http;
 }
 
-void qd_http_connector_process(qdpn_connector_t *c) {
-    qd_http_t * h = qdpn_listener_http(qdpn_connector_listener(c));
-    sys_mutex_lock(h->lock);
+static void http_connector_process(qdpn_connector_t *c) {
+    qd_http_listener_t *hl = qdpn_connector_http_listener(c);
+    qd_http_server_t *s = hl->server;
+    sys_mutex_lock(s->lock);
     int fd = qdpn_connector_get_fd(c);
-    struct lws *wsi = (struct lws*)qdpn_connector_http(c);
+    fd_data_t *d = fd_data(s, fd);
     /* Make sure we are still tracking this fd, could have been closed by timer */
-    if (wsi) {
+    if (d) {
         pn_transport_t *t = qdpn_connector_transport(c);
         int flags =
+            (qdpn_connector_hangup(c) ? POLLHUP : 0) |
             (qdpn_connector_activated(c, QDPN_CONNECTOR_READABLE) ? POLLIN : 0) |
             (qdpn_connector_activated(c, QDPN_CONNECTOR_WRITABLE) ? POLLOUT : 0);
         struct lws_pollfd pfd = { fd, flags, flags };
         if (pn_transport_pending(t) > 0) {
-            lws_callback_on_writable(wsi);
-        }
-        lws_service_fd(h->context, &pfd);
-        if (pn_transport_closed(t)) {
-            mark_closed(wsi);   /* Don't let the server close the FD. */
-        } else {
-            if (pn_transport_capacity(t) > 0)
-                qdpn_connector_activate(c, QDPN_CONNECTOR_READABLE);
-            if (pn_transport_pending(t) > 0 || lws_partial_buffered(wsi))
-                qdpn_connector_activate(c, QDPN_CONNECTOR_WRITABLE);
-            qdpn_connector_wakeup(c, pn_transport_tick(t, qdpn_now(NULL)));
+            lws_callback_on_writable(d->wsi);
         }
+        lws_service_fd(s->context, &pfd);
+        d = fd_data(s, fd);    /* We may have stopped tracking during service */
+        if (pn_transport_capacity(t) > 0)
+            qdpn_connector_activate(c, QDPN_CONNECTOR_READABLE);
+        if (pn_transport_pending(t) > 0 || (d && lws_partial_buffered(d->wsi)))
+            qdpn_connector_activate(c, QDPN_CONNECTOR_WRITABLE);
+        pn_timestamp_t wake = pn_transport_tick(t, qdpn_now(NULL));
+        if (wake) qdpn_connector_wakeup(c, wake);
     }
-    sys_mutex_unlock(h->lock);
-    check_timer(h);             /* Make sure the timer is running */
+    sys_mutex_unlock(s->lock);
+    check_timer(s);             /* Make sure the timer is running */
 }
 
-qd_http_connector_t *qd_http_connector(qd_http_t *h, qdpn_connector_t *c) {
-    if (set_fd(h, qdpn_connector_get_fd(c), c)) {
-        return NULL;
+/* Dispatch closes a connector because it is HUP, socket_error or transport_closed()  */
+static void http_connector_close(qdpn_connector_t *c) {
+    int fd = qdpn_connector_get_fd(c);
+    qd_http_server_t *s = qdpn_connector_http_listener(c)->server;
+    sys_mutex_lock(s->lock);
+    fd_data_t *d = fd_data(s, fd);
+    if (d) {                    /* Only if we are still tracking fd */
+        /* Shutdown but let LWS do the close(),  possibly in later timer */
+        shutdown(qdpn_connector_get_fd(c), SHUT_RDWR);
+        short flags = POLLIN|POLLOUT|POLLHUP;
+        struct lws_pollfd pfd = { qdpn_connector_get_fd(c), flags, flags };
+        lws_service_fd(s->context, &pfd);
+        qdpn_connector_mark_closed(c);
+        memset(d, 0 , sizeof(*d));
+    }
+    sys_mutex_unlock(s->lock);
+}
+
+static struct qdpn_connector_methods_t http_methods = {
+    http_connector_process,
+    http_connector_close
+};
+
+void qd_http_listener_accept(qd_http_listener_t *hl, qdpn_connector_t *c) {
+    qd_http_server_t *s = hl->server;
+    sys_mutex_lock(s->lock);
+    int fd = qdpn_connector_get_fd(c);
+    struct lws *wsi = lws_adopt_socket_vhost(hl->vhost, fd);
+    fd_data_t *d = fd_data(s, fd);
+    if (d) {          /* FD was adopted by LWS, so dispatch must not close it */
+        qdpn_connector_set_methods(c, &http_methods);
+        if (wsi) d->connector = c;
+    }
+    sys_mutex_unlock(s->lock);
+    if (!wsi) {       /* accept failed, dispatch should forget the FD. */
+        qdpn_connector_mark_closed(c);
     }
-    struct lws* wsi = lws_adopt_socket(h->context, qdpn_connector_get_fd(c));
-    return (qd_http_connector_t*)wsi;
 }
 
 static struct lws_protocols protocols[] = {
@@ -312,7 +344,7 @@ static struct lws_protocols protocols[] = {
         callback_http,
         0,
     },
-     /* "amqp" is the official oasis AMQP over WebSocket protocol name */
+    /* "amqp" is the official oasis AMQP over WebSocket protocol name */
     {
         "amqp",
         callback_amqpws,
@@ -326,31 +358,107 @@ static struct lws_protocols protocols[] = {
         callback_amqpws,
         sizeof(buffer_t),
     },
+    { NULL, NULL, 0, 0 } /* terminator */
 };
 
-qd_http_t *qd_http(qd_dispatch_t *d, qd_log_source_t *log) {
-    qd_http_t *h = calloc(1, sizeof(qd_http_t));
-    if (!h) return NULL;
-    h->lock = sys_mutex();
-    h->dispatch = d;
-    h->log = log;
-    lws_set_log_level(0, NULL);
+static qd_log_level_t qd_level(int lll) {
+    switch (lll) {
+    case LLL_ERR: return QD_LOG_ERROR;
+    case LLL_WARN: return QD_LOG_WARNING;
+    case LLL_NOTICE: return QD_LOG_INFO;
+    case LLL_INFO:return QD_LOG_DEBUG;
+    case LLL_DEBUG: return QD_LOG_TRACE;
+    default: return QD_LOG_NONE;
+    }
+}
+
+static void emit_lws_log(int lll, const char *line)  {
+    size_t  len = strlen(line);
+    while (len > 1 && isspace(line[len-1]))
+        --len;
+    qd_log(http_log, qd_level(lll), "%.*s", len, line);
+}
+
+qd_http_server_t *qd_http_server(qd_dispatch_t *d, qd_log_source_t *log) {
+    if (!http_log) http_log = qd_log_source("HTTP");
+    qd_http_server_t *s = calloc(1, sizeof(*s));
+    if (!s) return NULL;
+    s->log = log;
+    s->lock = sys_mutex();
+    s->dispatch = d;
+    int levels =
+        (qd_log_enabled(log, QD_LOG_ERROR) ? LLL_ERR : 0) |
+        (qd_log_enabled(log, QD_LOG_WARNING) ? LLL_WARN : 0) |
+        (qd_log_enabled(log, QD_LOG_INFO) ? LLL_NOTICE : 0) |
+        (qd_log_enabled(log, QD_LOG_DEBUG) ? LLL_INFO : 0) |
+        (qd_log_enabled(log, QD_LOG_TRACE) ? LLL_DEBUG : 0);
+    lws_set_log_level(levels, emit_lws_log);
 
     struct lws_context_creation_info info = {0};
-    info.port = CONTEXT_PORT_NO_LISTEN;
-    info.protocols = protocols;
     info.gid = info.uid = -1;
-    info.user = h;
-    info.mounts = &console_mount; /* Serve the console files */
+    info.user = s;
     info.server_string = QD_CONNECTION_PROPERTY_PRODUCT_VALUE;
-    h->context = lws_create_context(&info);
-    h->timer = NULL;            /* Can't init timer here, server not initialized. */
-    return h;
+    info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+        LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME |
+        LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+    info.max_http_header_pool = 32;
+    info.timeout_secs = 1;
+    s->context = lws_create_context(&info);
+    if (!s->context) {
+        free(s);
+        return NULL;
+    }
+    return s;
+}
+
+void qd_http_server_free(qd_http_server_t *s) {
+    sys_mutex_free(s->lock);
+    lws_context_destroy(s->context);
+    if (s->timer) qd_timer_free(s->timer);
+    if (s->fd) free(s->fd);
+    free(s);
+}
+
+qd_http_listener_t *qd_http_listener(qd_http_server_t *s, const qd_server_config_t *config) {
+    qd_http_listener_t *hl = calloc(1, sizeof(*hl));
+    if (!hl) return NULL;
+    hl->server = s;
+
+    struct lws_context_creation_info info = {0};
+
+    struct lws_http_mount *m = &hl->mount;
+    m->mountpoint = "/";    /* URL mount point */
+    m->mountpoint_len = strlen(m->mountpoint); /* length of the mountpoint */
+    m->origin = (config->http_root && *config->http_root) ? /* File system root */
+        config->http_root : QPID_CONSOLE_STAND_ALONE_INSTALL_DIR;
+    m->def = "index.html";  /* Default file name */
+    m->origin_protocol = LWSMPRO_FILE; /* mount type is a directory in a filesystem */
+    info.mounts = m;
+    info.port = CONTEXT_PORT_NO_LISTEN_SERVER; /* Don't use LWS listener */
+    info.protocols = protocols;
+    info.keepalive_timeout = 1;
+    info.ssl_cipher_list = CIPHER_LIST;
+    info.options |= LWS_SERVER_OPTION_VALIDATE_UTF8;
+    if (config->ssl_profile) {
+        info.ssl_cert_filepath = config->ssl_certificate_file;
+        info.ssl_private_key_filepath = config->ssl_private_key_file;
+        info.ssl_private_key_password = config->ssl_password;
+        info.ssl_ca_filepath = config->ssl_trusted_certificates;
+        info.options |=
+            LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+            (config->ssl_required ? 0 : LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT) |
+            (config->requireAuthentication ? LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT : 0);
+    }
+    snprintf(hl->name, sizeof(hl->name), "vhost%x", s->vhost_id++);
+    info.vhost_name = hl->name;
+    hl->vhost = lws_create_vhost(s->context, &info);
+    if (!hl->vhost) {
+        free(hl);
+        return NULL;
+    }
+    return hl;
 }
 
-void qd_http_free(qd_http_t *h) {
-    sys_mutex_free(h->lock);
-    if (h->timer) qd_timer_free(h->timer);
-    lws_context_destroy(h->context);
-    free(h);
+void qd_http_listener_free(qd_http_listener_t *hl) {
+    free(hl);
 }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/src/http.h
----------------------------------------------------------------------
diff --git a/src/http.h b/src/http.h
index bbfdc80..e38ffeb 100644
--- a/src/http.h
+++ b/src/http.h
@@ -20,14 +20,19 @@
  * under the License.
  */
 
-#include <qpid/dispatch/dispatch.h>
+typedef struct qd_http_listener_t qd_http_listener_t;
+typedef struct  qd_http_server_t qd_http_server_t;
+struct qd_dispatch_t;
+struct qd_log_source_t;
+struct qd_server_config_t;
+struct qdpn_connector_t;
 
-typedef struct qd_http_t qd_http_t;
-typedef struct qd_http_connector_t qd_http_connector_t;
-
-qd_http_t *qd_http(qd_dispatch_t *d, qd_log_source_t *log);
-void qd_http_free(qd_http_t *http);
-qd_http_connector_t *qd_http_connector(qd_http_t *h, qdpn_connector_t *c);
-void qd_http_connector_process(qdpn_connector_t *c);
+qd_http_server_t *qd_http_server(struct qd_dispatch_t *dispatch, struct qd_log_source_t *log);
+void qd_http_server_free(qd_http_server_t*);
+qd_http_listener_t *qd_http_listener(struct qd_http_server_t *s,
+                                     const struct qd_server_config_t *config);
+void qd_http_listener_free(qd_http_listener_t *hl);
+/* On error, qdpn_connector_closed(c) is true. */
+void qd_http_listener_accept(qd_http_listener_t *hl, struct qdpn_connector_t *c);
 
 #endif // QD_HTTP_H

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/src/posix/driver.c
----------------------------------------------------------------------
diff --git a/src/posix/driver.c b/src/posix/driver.c
index 741f500..1d18bbe 100644
--- a/src/posix/driver.c
+++ b/src/posix/driver.c
@@ -51,7 +51,6 @@
 #include <qpid/dispatch/ctools.h>
 #include "alloc.h"
 #include "aprintf.h"
-#include "http.h"
 #include "log_private.h"
 
 /* Decls */
@@ -72,9 +71,6 @@ const char *protocol_family_ipv6 = "IPv6";
 const char *AF_INET6_STR = "AF_INET6";
 const char *AF_INET_STR = "AF_INET";
 
-/* Connector processing function, direct or HTTP. */
-void (*process_fn)(qdpn_connector_t *c);
-
 static inline void ignore_result(int unused_result) {
     (void) unused_result;
 }
@@ -106,7 +102,6 @@ struct qdpn_listener_t {
     DEQ_LINKS(qdpn_listener_t);
     qdpn_driver_t *driver;
     void *context;
-    qd_http_t *http;
     int idx;
     int fd;
     bool pending:1;
@@ -125,8 +120,7 @@ struct qdpn_connector_t {
     pn_transport_t *transport;
     qdpn_listener_t *listener;
     void *context;
-    void (*process)(qdpn_connector_t *c);
-    qd_http_connector_t *http;
+    qdpn_connector_methods_t *methods;
     int idx;
     int fd;
     int status;
@@ -264,7 +258,6 @@ qdpn_listener_t *qdpn_listener(qdpn_driver_t *driver,
                                const char *host,
                                const char *port,
                                const char *protocol_family,
-                               qd_http_t *http,
                                void* context)
 {
     if (!driver) return NULL;
@@ -310,12 +303,11 @@ qdpn_listener_t *qdpn_listener(qdpn_driver_t *driver,
         return 0;
     }
 
-    qdpn_listener_t *l = qdpn_listener_fd(driver, sock, http, context);
+    qdpn_listener_t *l = qdpn_listener_fd(driver, sock, context);
     return l;
 }
 
-qdpn_listener_t *qdpn_listener_fd(qdpn_driver_t *driver, int fd,
-                                  qd_http_t *http, void *context)
+qdpn_listener_t *qdpn_listener_fd(qdpn_driver_t *driver, int fd, void *context)
 {
     if (!driver) return NULL;
 
@@ -328,7 +320,6 @@ qdpn_listener_t *qdpn_listener_fd(qdpn_driver_t *driver, int fd,
     l->fd = fd;
     l->closed = false;
     l->context = context;
-    l->http = http;
 
     qdpn_driver_add_listener(driver, l);
     return l;
@@ -417,10 +408,6 @@ qdpn_connector_t *qdpn_listener_accept(qdpn_listener_t *l,
     snprintf(c->name, PN_NAME_MAX, "%s", name);
     snprintf(c->hostip, PN_NAME_MAX, "%s", hostip);
     c->listener = l;
-    if (l->http) {
-        c->http = qd_http_connector(l->http, c);
-        c->process = qd_http_connector_process;
-    }
     return c;
 }
 
@@ -437,7 +424,6 @@ void qdpn_listener_close(qdpn_listener_t *l)
 void qdpn_listener_free(qdpn_listener_t *l)
 {
     if (!l) return;
-
     if (l->driver) qdpn_driver_remove_listener(l->driver, l);
     free_qdpn_listener_t(l);
 }
@@ -516,6 +502,12 @@ qdpn_connector_t *qdpn_connector(qdpn_driver_t *driver,
 
 
 static void connector_process(qdpn_connector_t *c);
+static void connector_close(qdpn_connector_t *c);
+
+static qdpn_connector_methods_t connector_methods = {
+    connector_process,
+    connector_close
+};
 
 qdpn_connector_t *qdpn_connector_fd(qdpn_driver_t *driver, int fd, void *context)
 {
@@ -540,7 +532,7 @@ qdpn_connector_t *qdpn_connector_fd(qdpn_driver_t *driver, int fd, void *context
     c->transport = pn_transport();
     c->context = context;
     c->listener = NULL;
-    c->process = connector_process;
+    c->methods = &connector_methods;
     qdpn_driver_add_connector(driver, c);
     return c;
 }
@@ -629,19 +621,18 @@ qdpn_listener_t *qdpn_connector_listener(qdpn_connector_t *ctor)
  */
 void qdpn_connector_mark_closed(qdpn_connector_t *ctor)
 {
-    if (!ctor) return;
+    if (!ctor || !ctor->driver) return;
     sys_mutex_lock(ctor->driver->lock);
     ctor->status = 0;
     if (!ctor->closed) {
         qd_log(ctor->driver->log, QD_LOG_TRACE, "closed %s", ctor->name);
         ctor->closed = true;
         ctor->driver->closed_count++;
-        ctor->http = NULL;
     }
     sys_mutex_unlock(ctor->driver->lock);
 }
 
-void qdpn_connector_close(qdpn_connector_t *ctor)
+static void connector_close(qdpn_connector_t *ctor)
 {
     if (ctor && !ctor->closed) {
         qdpn_connector_mark_closed(ctor);
@@ -650,6 +641,11 @@ void qdpn_connector_close(qdpn_connector_t *ctor)
     }
 }
 
+void qdpn_connector_close(qdpn_connector_t *c)
+{
+    if (c && !c->closed) c->methods->close(c);
+}
+
 bool qdpn_connector_closed(qdpn_connector_t *ctor)
 {
     return ctor ? ctor->closed : true;
@@ -698,6 +694,9 @@ void qdpn_activate_all(qdpn_driver_t *d)
     sys_mutex_unlock(d->lock);
 }
 
+bool qdpn_connector_hangup(qdpn_connector_t *ctor) {
+    return ctor->hangup;
+}
 
 bool qdpn_connector_activated(qdpn_connector_t *ctor, qdpn_activate_criteria_t crit)
 {
@@ -728,8 +727,7 @@ static pn_timestamp_t qdpn_connector_tick(qdpn_connector_t *ctor, pn_timestamp_t
 
 void qdpn_connector_process(qdpn_connector_t *c)
 {
-    if (!c || c->closed) return;
-    c->process(c);
+    if (c && !c->closed) c->methods->process(c);
 }
 
 static void connector_process(qdpn_connector_t *c)
@@ -954,7 +952,6 @@ int qdpn_driver_wait_3(qdpn_driver_t *d)
                 c->socket_error = true;
             }
             if (revents & POLLHUP) {
-                qd_log(c->driver->log, QD_LOG_TRACE, "hangup on %s", c->name);
                 c->hangup = true;
                 /* poll() is signalling POLLHUP. To see what happened we need
                  * to do an actual recv() to get the error code. But we might
@@ -1042,10 +1039,10 @@ qdpn_connector_t *qdpn_driver_connector(qdpn_driver_t *d)
     return NULL;
 }
 
-qd_http_connector_t *qdpn_connector_http(qdpn_connector_t* c) { return c->http; }
-
-void qdpn_connector_wakeup(qdpn_connector_t* c, pn_timestamp_t t) {
+void qdpn_connector_wakeup(qdpn_connector_t *c, pn_timestamp_t t) {
     c->wakeup = t;
 }
 
-qd_http_t *qdpn_listener_http(qdpn_listener_t* l) { return l->http; }
+void qdpn_connector_set_methods(qdpn_connector_t *c, qdpn_connector_methods_t *m) {
+    c->methods = m;
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/src/server.c
----------------------------------------------------------------------
diff --git a/src/server.c b/src/server.c
index 1e813aa..91338a0 100644
--- a/src/server.c
+++ b/src/server.c
@@ -573,8 +573,9 @@ static const char *log_incoming(char *buf, size_t size, qdpn_connector_t *cxtr)
     const char *cname = qdpn_connector_name(cxtr);
     const char *host = qd_listener->config->host;
     const char *port = qd_listener->config->port;
+    const char *protocol = qd_listener->config->http ? "HTTP" : "AMQP";
     snprintf(buf, size, "incoming %s connection from %s to %s:%s",
-             qdpn_connector_http(cxtr) ? "HTTP" : "AMQP", cname, host, port);
+             protocol, cname, host, port);
     return buf;
 }
 
@@ -627,6 +628,7 @@ static void thread_process_listeners_LH(qd_server_t *qd_server)
     qd_connection_t  *ctx;
 
     for (listener = qdpn_driver_listener(driver); listener; listener = qdpn_driver_listener(driver)) {
+        qd_listener_t *li = qdpn_listener_context(listener);
         bool policy_counted = false;
         cxtr = qdpn_listener_accept(listener, qd_server->qd->policy, &qd_policy_socket_accept, &policy_counted);
         if (!cxtr)
@@ -689,8 +691,14 @@ static void thread_process_listeners_LH(qd_server_t *qd_server)
             pn_transport_set_tracer(tport, qd_transport_tracer);
         }
 
-        // Set up SSL if configured
-        if (config->ssl_profile) {
+        if (li->http) {
+            // Set up HTTP if configured, HTTP provides its own SSL.
+            qd_log(qd_server->log_source, QD_LOG_TRACE, "Configuring HTTP%s on %s",
+                   config->ssl_profile ? "S" : "",
+                   log_incoming(logbuf, sizeof(logbuf), cxtr));
+            qd_http_listener_accept(li->http, cxtr);
+        } else if (config->ssl_profile)  {
+            // Set up SSL if configured and HTTP is not providing SSL.
             qd_log(qd_server->log_source, QD_LOG_TRACE, "Configuring SSL on %s",
                    log_incoming(logbuf, sizeof(logbuf), cxtr));
             if (listener_setup_ssl(ctx, config, tport) != QD_ERROR_NONE) {
@@ -1395,8 +1403,7 @@ qd_server_t *qd_server(qd_dispatch_t *qd, int thread_count, const char *containe
     qd_server->heartbeat_timer        = 0;
     qd_server->next_connection_id     = 1;
     qd_server->py_displayname_obj     = 0;
-    qd_server->http = qd_http(qd, qd_server->log_source);
-
+    qd_server->http                   = qd_http_server(qd, qd_server->log_source);
     qd_log(qd_server->log_source, QD_LOG_INFO, "Container Name: %s", qd_server->container_name);
 
     return qd_server;
@@ -1406,9 +1413,9 @@ qd_server_t *qd_server(qd_dispatch_t *qd, int thread_count, const char *containe
 void qd_server_free(qd_server_t *qd_server)
 {
     if (!qd_server) return;
-    qd_http_free(qd_server->http);
     for (int i = 0; i < qd_server->thread_count; i++)
         thread_free(qd_server->threads[i]);
+    qd_http_server_free(qd_server->http);
     qd_timer_finalize();
     qdpn_driver_free(qd_server->driver);
     sys_mutex_free(qd_server->lock);
@@ -1699,23 +1706,27 @@ qd_listener_t *qd_server_listen(qd_dispatch_t *qd, const qd_server_config_t *con
     li->server      = qd_server;
     li->config      = config;
     li->context     = context;
-    qd_http_t *http = NULL;
+    li->http = NULL;
     if (config->http) {
-        http = qd->server->http;
-        if (!http) {
-            qd_log(qd_server->log_source, QD_LOG_CRITICAL, "HTTP support not available for %s:%s",
+        li->http = qd_http_listener(qd_server->http, config);
+        if (!li->http) {
+            free_qd_listener_t(li);
+            qd_log(qd_server->log_source, QD_LOG_ERROR, "Cannot start HTTP listener on %s:%s",
                    config->host, config->port);
+            return NULL;
         }
     }
     li->pn_listener = qdpn_listener(
-        qd_server->driver, config->host, config->port, config->protocol_family, http, li);
+        qd_server->driver, config->host, config->port, config->protocol_family, li);
 
     if (!li->pn_listener) {
         free_qd_listener_t(li);
-        return 0;
+        qd_log(qd_server->log_source, QD_LOG_ERROR, "Cannot start listener on %s:%s",
+               config->host, config->port);
+        return NULL;
     }
     qd_log(qd_server->log_source, QD_LOG_TRACE, "Listening on %s:%s%s", config->host, config->port,
-           config->http ? "(http)":"");
+           config->http ? (config->ssl_profile ? "(HTTPS)":"(HTTP)") : "");
 
     return li;
 }
@@ -1725,7 +1736,7 @@ void qd_server_listener_free(qd_listener_t* li)
 {
     if (!li)
         return;
-
+    if (li->http) qd_http_listener_free(li->http);
     qdpn_listener_free(li->pn_listener);
     free_qd_listener_t(li);
 }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/src/server_private.h
----------------------------------------------------------------------
diff --git a/src/server_private.h b/src/server_private.h
index 8c4d89e..2508bb2 100644
--- a/src/server_private.h
+++ b/src/server_private.h
@@ -60,6 +60,7 @@ struct qd_listener_t {
     const qd_server_config_t *config;
     void                     *context;
     qdpn_listener_t          *pn_listener;
+    qd_http_listener_t       *http;
 };
 
 
@@ -192,7 +193,7 @@ struct qd_server_t {
     qd_timer_t               *heartbeat_timer;
     uint64_t                 next_connection_id;
     void                     *py_displayname_obj;
-    qd_http_t                *http;
+    qd_http_server_t         *http;
 };
 
 ALLOC_DECLARE(qd_work_item_t);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d2c6159..a1d2520 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -68,6 +68,10 @@ add_test(router_engine_test    ${TEST_WRAP} -m unittest -v router_engine_test)
 add_test(management_test       ${TEST_WRAP} -m unittest -v management)
 add_test(router_policy_test    ${TEST_WRAP} -m unittest -v router_policy_test)
 
+if(USE_LIBWEBSOCKETS)
+  set(SYSTEM_TESTS_HTTP system_tests_http)
+endif()
+
 # System test python modules
 foreach(py_test_module
 #   system_tests_broker
@@ -87,7 +91,9 @@ foreach(py_test_module
     system_tests_deprecated
     system_tests_two_routers
     system_tests_three_routers
-    system_tests_multi_tenancy)
+    system_tests_multi_tenancy
+    ${SYSTEM_TESTS_HTTP}
+    )
 
   add_test(${py_test_module} ${TEST_WRAP} -m unittest -v ${py_test_module})
   list(APPEND SYSTEM_TEST_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${py_test_module}.py)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/tests/system_tests_http.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_http.py b/tests/system_tests_http.py
new file mode 100644
index 0000000..8ae1b82
--- /dev/null
+++ b/tests/system_tests_http.py
@@ -0,0 +1,120 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest, os, json, threading, sys, ssl, urllib2
+import ssl
+from subprocess import PIPE, Popen, STDOUT
+from system_test import TestCase, Qdrouterd, main_module, DIR, TIMEOUT, Process
+from qpid_dispatch.management.client import Node
+
+class RouterTestHttp(TestCase):
+
+    @staticmethod
+    def ssl_file(name):
+        return os.path.join(DIR, 'ssl_certs', name)
+
+    @classmethod
+    def get(cls, url):
+        return urllib2.urlopen(url, cafile=cls.ssl_file('ca-certificate.pem')).read()
+
+    @classmethod
+    def get_cert(cls, url):
+        context = ssl.create_default_context()
+        context.load_cert_chain(cls.ssl_file('client-certificate.pem'),
+                                cls.ssl_file('client-private-key.pem'),
+                                'client-password')
+        context.load_verify_locations(cls.ssl_file('ca-certificate.pem'))
+        opener = urllib2.build_opener(urllib2.HTTPSHandler(context=context))
+        return opener.open(url).read()
+
+    def assert_get(self, url):
+        self.assertEqual("HTTP test\n", self.get("%s/system_tests_http.txt" % url))
+
+    def assert_get_cert(self, url):
+        self.assertEqual("HTTP test\n", self.get_cert("%s/system_tests_http.txt" % url))
+
+    def test_http_get(self):
+        config = Qdrouterd.Config([
+            ('router', {'id': 'QDR.HTTP'}),
+            ('listener', {'port': self.get_port(), 'httpRoot': os.path.dirname(__file__)}),
+            ('listener', {'port': self.get_port(), 'httpRoot': os.path.dirname(__file__)}),
+        ])
+        r = self.qdrouterd('http-test-router', config)
+
+        def test(port):
+            self.assert_get("http://localhost:%d" % port)
+            self.assertRaises(urllib2.HTTPError, urllib2.urlopen, "http://localhost:%d/nosuch" % port)
+
+        # Sequential calls on multiple ports
+        for port in r.ports: test(port)
+
+        # Concurrent calls on multiple ports
+        class TestThread(threading.Thread):
+            def __init__(self, port):
+                threading.Thread.__init__(self)
+                self.port, self.ex = port, None
+                self.start()
+            def run(self):
+                try: test(self.port)
+                except Exception, e: self.ex = e
+        threads = [TestThread(p) for p in r.ports + r.ports]
+        for t in threads: t.join()
+        for t in threads:
+            if t.ex: raise t.ex
+
+        # https not configured
+        self.assertRaises(urllib2.URLError, urllib2.urlopen, "https://localhost:%d/nosuch" % r.ports[0])
+
+    def test_https_get(self):
+        def listener(**kwargs):
+            args = dict(kwargs)
+            args.update({'port': self.get_port(), 'httpRoot': os.path.dirname(__file__)})
+            return ('listener', args)
+
+        config = Qdrouterd.Config([
+            ('router', {'id': 'QDR.HTTPS'}),
+            ('sslProfile', {'name': 'simple-ssl',
+                            'certDb': self.ssl_file('ca-certificate.pem'),
+                            'certFile': self.ssl_file('server-certificate.pem'),
+                            'keyFile': self.ssl_file('server-private-key.pem'),
+                            'password': 'server-password'
+            }),
+            listener(sslProfile='simple-ssl', requireSsl=False, authenticatePeer=False),
+            listener(sslProfile='simple-ssl', requireSsl=True, authenticatePeer=False),
+            listener(sslProfile='simple-ssl', requireSsl=True, authenticatePeer=True)])
+        # saslMechanisms='EXTERNAL'
+
+        r = self.qdrouterd('https-test-router', config)
+        r.wait_ready()
+
+        self.assert_get("https://localhost:%s" % r.ports[0])
+        # requireSsl=false Allows simple-ssl HTTP
+        self.assert_get("http://localhost:%s" % r.ports[0])
+
+        self.assert_get("https://localhost:%s" % r.ports[1])
+        # requireSsl=True does not allow simple-ssl HTTP
+        self.assertRaises(Exception, self.assert_get, "http://localhost:%s" % r.ports[1])
+
+        # authenticatePeer=True requires a client cert
+        self.assertRaises(urllib2.URLError, self.assert_get, "https://localhost:%s" % r.ports[2])
+        # Provide client cert
+        self.assert_get_cert("https://localhost:%d" % r.ports[2])
+
+if __name__ == '__main__':
+    unittest.main(main_module())

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/9442bb0b/tests/system_tests_http.txt
----------------------------------------------------------------------
diff --git a/tests/system_tests_http.txt b/tests/system_tests_http.txt
new file mode 100644
index 0000000..4cb62d8
--- /dev/null
+++ b/tests/system_tests_http.txt
@@ -0,0 +1 @@
+HTTP test


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


Mime
View raw message