[libsoup/websocket: 11/11] websockets: add WebSocket support to SoupSession and SoupServer



commit a08942df00a8115901f19593734a3d2b1efbdb3e
Author: Dan Winship <danw gnome org>
Date:   Sun Nov 30 10:26:23 2014 -0500

    websockets: add WebSocket support to SoupSession and SoupServer
    
    Add new SoupSession and SoupServer API to use the new WebSocket
    support.
    
    Based on code originally from the Cockpit project, and on earlier work
    by Lionel Landwerlin to merge that into libsoup.

 libsoup/libsoup-2.4.sym |    3 +
 libsoup/soup-server.c   |  266 ++++++++++++++++++++++++++++++++++++-----------
 libsoup/soup-server.h   |   15 +++
 libsoup/soup-session.c  |  131 +++++++++++++++++++++++
 libsoup/soup-session.h  |   15 +++
 tests/websocket-test.c  |  214 +++++++++++++++++++++++++++++++++++++
 6 files changed, 583 insertions(+), 61 deletions(-)
---
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index 4f52b84..69d46ef 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -373,6 +373,7 @@ soup_server_accept_iostream
 soup_server_add_auth_domain
 soup_server_add_early_handler
 soup_server_add_handler
+soup_server_add_websocket_handler
 soup_server_disconnect
 soup_server_get_async_context
 soup_server_get_listener
@@ -438,6 +439,8 @@ soup_session_sync_get_type
 soup_session_sync_new
 soup_session_sync_new_with_options
 soup_session_unpause_message
+soup_session_websocket_connect_async
+soup_session_websocket_connect_finish
 soup_session_would_redirect
 soup_socket_connect_async
 soup_socket_connect_sync
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c
index 6a9f0c8..05f4de2 100644
--- a/libsoup/soup-server.c
+++ b/libsoup/soup-server.c
@@ -19,6 +19,8 @@
 #include "soup-misc-private.h"
 #include "soup-path-map.h" 
 #include "soup-socket-private.h"
+#include "soup-websocket.h"
+#include "soup-websocket-connection.h"
 
 /**
  * SECTION:soup-server
@@ -68,16 +70,26 @@
  * #SoupServer will return a %SOUP_STATUS_CONTINUE status before
  * continuing.)
  *
- * The server will then read in the response body (if present), and
- * then (assuming no previous step assigned a status to the message)
- * call any "normal" handler (added with soup_server_add_handler())
- * for the message's Request-URI.
+ * The server will then read in the response body (if present). At
+ * this point, if there are no handlers at all defined for the
+ * Request-URI, then the server will return %SOUP_STATUS_NOT_FOUND to
+ * the client.
  *
- * If the message still has no status code after this step (and has
- * not been paused with soup_server_pause_message()), then it will
- * automatically be given a status of %SOUP_STATUS_NOT_FOUND (if there
- * was no handler for the path) or %SOUP_STATUS_INTERNAL_SERVER_ERROR
- * (if a handler ran but did not assign a status).
+ * Otherwise (assuming no previous step assigned a status to the
+ * message) any "normal" handlers (added with
+ * soup_server_add_handler()) for the message's Request-URI will be
+ * run.
+ *
+ * Then, if the path has a WebSocket handler registered (and has
+ * not yet been assigned a status), #SoupServer will attempt to
+ * validate the WebSocket handshake, filling in the response and
+ * setting a status of %SOUP_STATUS_SWITCHING_PROTOCOLS or
+ * %SOUP_STATUS_BAD_REQUEST accordingly.
+ *
+ * If the message still has no status code at this point (and has not
+ * been paused with soup_server_pause_message()), then it will be
+ * given a status of %SOUP_STATUS_INTERNAL_SERVER_ERROR (because at
+ * least one handler ran, but returned without assigning a status).
  *
  * Finally, the server will emit #SoupServer::request-finished (or
  * #SoupServer::request-aborted if an I/O error occurred before
@@ -141,6 +153,12 @@ typedef struct {
        SoupServerCallback  callback;
        GDestroyNotify      destroy;
        gpointer            user_data;
+
+       char                         *websocket_origin;
+       char                        **websocket_protocols;
+       SoupServerWebsocketCallback   websocket_callback;
+       GDestroyNotify                websocket_destroy;
+       gpointer                      websocket_user_data;
 } SoupServerHandler;
 
 typedef struct {
@@ -157,7 +175,7 @@ typedef struct {
 
        gboolean           raw_paths;
        SoupPathMap       *handlers;
-       
+
        GSList            *auth_domains;
 
        char             **http_aliases, **https_aliases;
@@ -198,10 +216,14 @@ static void
 free_handler (SoupServerHandler *handler)
 {
        g_free (handler->path);
+       g_free (handler->websocket_origin);
+       g_strfreev (handler->websocket_protocols);
        if (handler->early_destroy)
                handler->early_destroy (handler->early_user_data);
        if (handler->destroy)
                handler->destroy (handler->user_data);
+       if (handler->websocket_destroy)
+               handler->websocket_destroy (handler->websocket_user_data);
        g_slice_free (SoupServerHandler, handler);
 }
 
@@ -1201,28 +1223,33 @@ request_finished (SoupMessage *msg, SoupMessageIOCompletion completion, gpointer
  */
 #define NORMALIZED_PATH(path) ((path) && *(path) ? (path) : "/")
 
-/* Returns TRUE if a handler exists, FALSE if not */
-static gboolean
-call_handler (SoupServer *server, SoupClientContext *client,
-             SoupMessage *msg, gboolean early)
+static SoupServerHandler *
+get_handler (SoupServer *server, SoupMessage *msg)
 {
        SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
-       SoupServerHandler *handler;
        SoupURI *uri;
-       GHashTable *form_data_set;
 
        uri = soup_message_get_uri (msg);
-       handler = soup_path_map_lookup (priv->handlers, NORMALIZED_PATH (uri->path));
-       if (!handler)
-               return FALSE;
-       else if (early && !handler->early_callback)
-               return TRUE;
+       return soup_path_map_lookup (priv->handlers, NORMALIZED_PATH (uri->path));
+}
+
+static void
+call_handler (SoupServer *server, SoupServerHandler *handler,
+             SoupClientContext *client, SoupMessage *msg,
+             gboolean early)
+{
+       GHashTable *form_data_set;
+       SoupURI *uri;
+
+       if (early && !handler->early_callback)
+               return;
        else if (!early && !handler->callback)
-               return TRUE;
+               return;
 
        if (msg->status_code != 0)
-               return TRUE;
+               return;
 
+       uri = soup_message_get_uri (msg);
        if (uri->query)
                form_data_set = soup_form_decode (uri->query);
        else
@@ -1240,8 +1267,6 @@ call_handler (SoupServer *server, SoupClientContext *client,
 
        if (form_data_set)
                g_hash_table_unref (form_data_set);
-
-       return TRUE;
 }
 
 static void
@@ -1249,6 +1274,7 @@ got_headers (SoupMessage *msg, SoupClientContext *client)
 {
        SoupServer *server = client->server;
        SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
+       SoupServerHandler *handler;
        SoupURI *uri;
        SoupDate *date;
        char *date_string;
@@ -1324,22 +1350,67 @@ got_headers (SoupMessage *msg, SoupClientContext *client)
        }
 
        /* Otherwise, call the early handlers. */
-       call_handler (server, client, msg, TRUE);
+       handler = get_handler (server, msg);
+       if (handler)
+               call_handler (server, handler, client, msg, TRUE);
+}
+
+static void
+complete_websocket_upgrade (SoupMessage *msg, gpointer user_data)
+{
+       SoupClientContext *client = user_data;
+       SoupServer *server = client->server;
+       SoupURI *uri = soup_message_get_uri (msg);
+       SoupServerHandler *handler;
+       GIOStream *stream;
+       SoupWebsocketConnection *conn;
+
+       handler = get_handler (server, msg);
+       if (!handler || !handler->websocket_callback)
+               return;
+
+       stream = soup_client_context_steal_connection (client);
+       conn = soup_websocket_connection_new (stream, uri,
+                                             SOUP_WEBSOCKET_CONNECTION_SERVER,
+                                             soup_message_headers_get_one (msg->request_headers, "Origin"),
+                                             soup_message_headers_get_one (msg->response_headers, 
"Sec-WebSocket-Protocol"));
+       g_object_unref (stream);
+       soup_client_context_unref (client);
+
+       (*handler->websocket_callback) (server, conn, uri->path, client,
+                                       handler->websocket_user_data);
+       g_object_unref (conn);
 }
 
 static void
 got_body (SoupMessage *msg, SoupClientContext *client)
 {
        SoupServer *server = client->server;
+       SoupServerHandler *handler;
 
        g_signal_emit (server, signals[REQUEST_READ], 0, msg, client);
 
        if (msg->status_code != 0)
                return;
 
-       if (!call_handler (server, client, msg, FALSE)) {
-               if (msg->status_code == 0)
-                       soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+       handler = get_handler (server, msg);
+       if (!handler) {
+               soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+               return;
+       }
+
+       call_handler (server, handler, client, msg, FALSE);
+       if (msg->status_code != 0)
+               return;
+
+       if (handler->websocket_callback && msg->status_code == 0) {
+               if (soup_websocket_server_process_handshake (msg,
+                                                            handler->websocket_origin,
+                                                            handler->websocket_protocols)) {
+                       g_signal_connect (msg, "wrote-informational",
+                                         G_CALLBACK (complete_websocket_upgrade),
+                                         soup_client_context_ref (client));
+               }
        }
 }
 
@@ -2395,40 +2466,23 @@ soup_client_context_steal_connection (SoupClientContext *client)
  * for details of what handlers can/should do.
  **/
 
-static void
-add_handler_internal (SoupServer            *server,
-                     const char            *path,
-                     gboolean               early,
-                     SoupServerCallback     callback,
-                     gpointer               user_data,
-                     GDestroyNotify         destroy)
+static SoupServerHandler *
+get_or_create_handler (SoupServer *server, const char *exact_path)
 {
        SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
        SoupServerHandler *handler;
 
-       path = NORMALIZED_PATH (path);
+       exact_path = NORMALIZED_PATH (exact_path);
 
-       handler = soup_path_map_lookup (priv->handlers, path);
-       if (handler && !strcmp (handler->path, path)) {
-               if (early && handler->early_destroy)
-                       handler->early_destroy (handler->early_user_data);
-               else if (!early && handler->destroy)
-                       handler->destroy (handler->user_data);
-       } else {
-               handler = g_slice_new0 (SoupServerHandler);
-               handler->path = g_strdup (path);
-               soup_path_map_add (priv->handlers, path, handler);
-       }
+       handler = soup_path_map_lookup (priv->handlers, exact_path);
+       if (handler && !strcmp (handler->path, exact_path))
+               return handler;
 
-       if (early) {
-               handler->early_callback   = callback;
-               handler->early_destroy    = destroy;
-               handler->early_user_data  = user_data;
-       } else {
-               handler->callback   = callback;
-               handler->destroy    = destroy;
-               handler->user_data  = user_data;
-       }
+       handler = g_slice_new0 (SoupServerHandler);
+       handler->path = g_strdup (exact_path);
+       soup_path_map_add (priv->handlers, exact_path, handler);
+
+       return handler;
 }
 
 /**
@@ -2484,11 +2538,18 @@ soup_server_add_handler (SoupServer            *server,
                         gpointer               user_data,
                         GDestroyNotify         destroy)
 {
+       SoupServerHandler *handler;
+
        g_return_if_fail (SOUP_IS_SERVER (server));
        g_return_if_fail (callback != NULL);
 
-       add_handler_internal (server, path, FALSE,
-                             callback, user_data, destroy);
+       handler = get_or_create_handler (server, path);
+       if (handler->destroy)
+               handler->destroy (handler->user_data);
+
+       handler->callback   = callback;
+       handler->destroy    = destroy;
+       handler->user_data  = user_data;
 }
 
 /**
@@ -2536,11 +2597,94 @@ soup_server_add_early_handler (SoupServer            *server,
                               gpointer               user_data,
                               GDestroyNotify         destroy)
 {
+       SoupServerHandler *handler;
+
+       g_return_if_fail (SOUP_IS_SERVER (server));
+       g_return_if_fail (callback != NULL);
+
+       handler = get_or_create_handler (server, path);
+       if (handler->early_destroy)
+               handler->early_destroy (handler->early_user_data);
+
+       handler->early_callback   = callback;
+       handler->early_destroy    = destroy;
+       handler->early_user_data  = user_data;
+}
+
+/**
+ * SoupServerWebsocketCallback:
+ * @server: the #SoupServer
+ * @path: the path component of @msg's Request-URI
+ * @connection: the newly created WebSocket connection
+ * @client: additional contextual information about the client
+ * @user_data: the data passed to @soup_server_add_handler
+ *
+ * A callback used to handle WebSocket requests to a #SoupServer. The
+ * callback will be invoked after sending the handshake response back
+ * to the client (and is only invoked if the handshake was
+ * successful).
+ *
+ * @path contains the path of the Request-URI, subject to the same
+ * rules as #SoupServerCallback (qv).
+ **/
+
+/**
+ * soup_server_add_websocket_handler:
+ * @server: a #SoupServer
+ * @path: (allow-none): the toplevel path for the handler
+ * @origin: (allow-none): the origin of the connection
+ * @protocols: (allow-none) (array zero-terminated=1): the protocols
+ *   supported by this handler
+ * @callback: callback to invoke for successful WebSocket requests under @path
+ * @user_data: data for @callback
+ * @destroy: destroy notifier to free @user_data
+ *
+ * Adds a WebSocket handler to @server for requests under @path. (If
+ * @path is %NULL or "/", then this will be the default handler for
+ * all requests that don't have a more specific handler.)
+ *
+ * When a path has a WebSocket handler registered, @server will check
+ * incoming requests for WebSocket handshakes after all other handlers
+ * have run (unless some earlier handler has already set a status code
+ * on the message), and update the request's status, response headers,
+ * and response body accordingly.
+ *
+ * If @origin is non-%NULL, then only requests containing a matching
+ * "Origin" header will be accepted. If @protocols is non-%NULL, then
+ * only requests containing a compatible "Sec-WebSocket-Protocols"
+ * header will be accepted. More complicated requirements can be
+ * handled by adding a normal handler to @path, and having it perform
+ * whatever checks are needed (possibly calling
+ * soup_server_check_websocket_handshake() one or more times), and
+ * setting a failure status code if the handshake should be rejected.
+ **/
+void
+soup_server_add_websocket_handler (SoupServer                   *server,
+                                  const char                   *path,
+                                  const char                   *origin,
+                                  char                        **protocols,
+                                  SoupServerWebsocketCallback   callback,
+                                  gpointer                      user_data,
+                                  GDestroyNotify                destroy)
+{
+       SoupServerHandler *handler;
+
        g_return_if_fail (SOUP_IS_SERVER (server));
        g_return_if_fail (callback != NULL);
 
-       add_handler_internal (server, path, TRUE,
-                             callback, user_data, destroy);
+       handler = get_or_create_handler (server, path);
+       if (handler->websocket_destroy)
+               handler->websocket_destroy (handler->websocket_user_data);
+       if (handler->websocket_origin)
+               g_free (handler->websocket_origin);
+       if (handler->websocket_protocols)
+               g_strfreev (handler->websocket_protocols);
+
+       handler->websocket_callback   = callback;
+       handler->websocket_destroy    = destroy;
+       handler->websocket_user_data  = user_data;
+       handler->websocket_origin     = g_strdup (origin);
+       handler->websocket_protocols  = g_strdupv (protocols);
 }
 
 /**
diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h
index 3cabc03..36a9490 100644
--- a/libsoup/soup-server.h
+++ b/libsoup/soup-server.h
@@ -8,6 +8,7 @@
 
 #include <libsoup/soup-types.h>
 #include <libsoup/soup-uri.h>
+#include <libsoup/soup-websocket-connection.h>
 
 G_BEGIN_DECLS
 
@@ -131,6 +132,20 @@ void            soup_server_add_early_handler  (SoupServer         *server,
                                                gpointer            user_data,
                                                GDestroyNotify      destroy);
 
+typedef void (*SoupServerWebsocketCallback) (SoupServer              *server,
+                                            SoupWebsocketConnection *connection,
+                                            const char              *path,
+                                            SoupClientContext       *client,
+                                            gpointer                 user_data);
+SOUP_AVAILABLE_IN_2_50
+void            soup_server_add_websocket_handler (SoupServer                   *server,
+                                                  const char                   *path,
+                                                  const char                   *origin,
+                                                  char                        **protocols,
+                                                  SoupServerWebsocketCallback   callback,
+                                                  gpointer                      user_data,
+                                                  GDestroyNotify                destroy);
+
 void            soup_server_remove_handler     (SoupServer         *server,
                                                const char         *path);
 
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index c12c151..4c768ac 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -22,6 +22,8 @@
 #include "soup-proxy-resolver-wrapper.h"
 #include "soup-session-private.h"
 #include "soup-socket-private.h"
+#include "soup-websocket.h"
+#include "soup-websocket-connection.h"
 
 #define HOST_KEEP_ALIVE 5 * 60 * 1000 /* 5 min in msecs */
 
@@ -4723,3 +4725,132 @@ soup_session_steal_connection (SoupSession *session,
        soup_message_queue_item_unref (item);
        return stream;
 }
+
+static void websocket_connect_async_stop (SoupMessage *msg, gpointer user_data);
+
+static void
+websocket_connect_async_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+       GTask *task = user_data;
+
+       g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (websocket_connect_async_stop), task);
+
+       g_task_return_new_error (task,
+                                SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
+                                "%s", _("The server did not accept the WebSocket handshake."));
+       g_object_unref (task);
+}
+
+static void
+websocket_connect_async_stop (SoupMessage *msg, gpointer user_data)
+{
+       GTask *task = user_data;
+       SoupMessageQueueItem *item = g_task_get_task_data (task);
+       GIOStream *stream;
+       SoupWebsocketConnection *client;
+       GError *error = NULL;
+
+       g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (websocket_connect_async_stop), task);
+
+       if (soup_websocket_client_verify_handshake (item->msg, &error)){
+               stream = soup_session_steal_connection (item->session, item->msg);
+               client = soup_websocket_connection_new (stream, 
+                                                       soup_message_get_uri (item->msg),
+                                                       SOUP_WEBSOCKET_CONNECTION_CLIENT,
+                                                       soup_message_headers_get_one (msg->request_headers, 
"Origin"),
+                                                       soup_message_headers_get_one (msg->response_headers, 
"Sec-WebSocket-Protocol"));
+               g_object_unref (stream);
+
+               g_task_return_pointer (task, client, g_object_unref);
+       } else
+               g_task_return_error (task, error);
+       g_object_unref (task);
+}
+
+/**
+ * soup_session_websocket_connect_async:
+ * @session: a #SoupSession
+ * @msg: #SoupMessage indicating the WebSocket server to connect to
+ * @origin: (allow-none): origin of the connection
+ * @protocols: (allow-none) (array zero-terminated=1): a
+ *   %NULL-terminated array of protocols supported
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously creates a #SoupWebsocketConnection to communicate
+ * with a remote server.
+ *
+ * All necessary WebSocket-related headers will be added to @msg, and
+ * it will then be sent and asynchronously processed normally
+ * (including handling of redirection and HTTP authentication).
+ *
+ * If the server returns "101 Switching Protocols", then @msg's status
+ * code and response headers will be updated, and then the WebSocket
+ * handshake will be completed. On success,
+ * soup_websocket_connect_finish() will return a new
+ * #SoupWebsocketConnection. On failure it will return a #GError.
+ *
+ * If the server returns a status other than "101 Switching
+ * Protocols", then @msg will contain the complete response headers
+ * and body from the server's response, and
+ * soup_websocket_connect_finish() will return
+ * %SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET.
+ *
+ * Since: 2.50
+ */
+void
+soup_session_websocket_connect_async (SoupSession          *session,
+                                     SoupMessage          *msg,
+                                     const char           *origin,
+                                     char                **protocols,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data)
+{
+       SoupMessageQueueItem *item;
+       GTask *task;
+
+       g_return_if_fail (SOUP_IS_SESSION (session));
+       g_return_if_fail (SOUP_SESSION_GET_PRIVATE (session)->use_thread_context);
+       g_return_if_fail (SOUP_IS_MESSAGE (msg));
+
+       soup_websocket_client_prepare_handshake (msg, origin, protocols);
+
+       task = g_task_new (session, cancellable, callback, user_data);
+       item = soup_session_append_queue_item (session, msg, TRUE, FALSE,
+                                              websocket_connect_async_complete, task);
+       g_task_set_task_data (task, item, (GDestroyNotify) soup_message_queue_item_unref);
+
+       soup_message_add_status_code_handler (msg, "got-informational",
+                                             SOUP_STATUS_SWITCHING_PROTOCOLS,
+                                             G_CALLBACK (websocket_connect_async_stop), task);
+       soup_session_kick_queue (session);
+}
+
+/**
+ * soup_session_websocket_connect_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the #SoupWebsocketConnection response to a
+ * soup_session_websocket_connect_async() call and (if successful),
+ * returns a #SoupWebsockConnection that can be used to communicate
+ * with the server.
+ *
+ * Return value: (transfer full): a new #SoupWebsocketConnection, or
+ *   %NULL on error.
+ *
+ * Since: 2.50
+ */
+SoupWebsocketConnection *
+soup_session_websocket_connect_finish (SoupSession      *session,
+                                      GAsyncResult     *result,
+                                      GError          **error)
+{
+       g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+       g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+       return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index 5dcd747..4c64ce4 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -9,6 +9,7 @@
 #include <libsoup/soup-types.h>
 #include <libsoup/soup-address.h>
 #include <libsoup/soup-message.h>
+#include <libsoup/soup-websocket-connection.h>
 
 G_BEGIN_DECLS
 
@@ -210,6 +211,20 @@ SOUP_AVAILABLE_IN_2_50
 GIOStream *soup_session_steal_connection (SoupSession *session,
                                          SoupMessage *msg);
 
+SOUP_AVAILABLE_IN_2_50
+void                     soup_session_websocket_connect_async  (SoupSession          *session,
+                                                               SoupMessage          *msg,
+                                                               const char           *origin,
+                                                               char                **protocols,
+                                                               GCancellable         *cancellable,
+                                                               GAsyncReadyCallback   callback,
+                                                               gpointer              user_data);
+
+SOUP_AVAILABLE_IN_2_50
+SoupWebsocketConnection *soup_session_websocket_connect_finish (SoupSession          *session,
+                                                               GAsyncResult         *result,
+                                                               GError              **error);
+
 G_END_DECLS
 
 #endif /* SOUP_SESSION_H */
diff --git a/tests/websocket-test.c b/tests/websocket-test.c
index 7e6ae05..c3aa142 100644
--- a/tests/websocket-test.c
+++ b/tests/websocket-test.c
@@ -29,7 +29,12 @@ typedef struct {
        GSocket *listener;
        gushort port;
 
+       SoupSession *session;
+       SoupMessage *msg;
        SoupWebsocketConnection *client;
+       GError *client_error;
+
+       SoupServer *soup_server;
        SoupWebsocketConnection *server;
 
        gboolean no_server;
@@ -187,6 +192,96 @@ teardown_direct_connection (Test *test,
 }
 
 static void
+setup_soup_server (Test *test,
+                  const char *origin,
+                  const char **protocols,
+                  SoupServerWebsocketCallback callback,
+                  gpointer user_data)
+{
+       GError *error = NULL;
+
+       setup_listener (test);
+
+       test->soup_server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+       soup_server_listen_socket (test->soup_server, test->listener, 0, &error);
+       g_assert_no_error (error);
+
+       soup_server_add_websocket_handler (test->soup_server, "/unix",
+                                          origin, (char **) protocols,
+                                          callback, user_data, NULL);
+}
+
+static void
+client_connect (Test *test,
+               const char *origin,
+               const char **protocols,
+               GAsyncReadyCallback callback,
+               gpointer user_data)
+{
+       char *url;
+
+       if (!test->session)
+               test->session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+       url = g_strdup_printf ("ws://127.0.0.1:%u/unix", test->port);
+       test->msg = soup_message_new ("GET", url);
+       g_free (url);
+
+       soup_session_websocket_connect_async (test->session, test->msg,
+                                             origin, (char **) protocols,
+                                             NULL, callback, user_data);
+}
+
+static void
+got_server_connection (SoupServer              *server,
+                      SoupWebsocketConnection *connection,
+                      const char              *path,
+                      SoupClientContext       *client,
+                      gpointer                 user_data)
+{
+       Test *test = user_data;
+
+       test->server = g_object_ref (connection);
+}
+
+static void
+got_client_connection (GObject *object,
+                      GAsyncResult *result,
+                      gpointer user_data)
+{
+       Test *test = user_data;
+
+       test->client = soup_session_websocket_connect_finish (SOUP_SESSION (object),
+                                                             result, &test->client_error);
+}
+
+static void
+setup_soup_connection (Test *test,
+                      gconstpointer data)
+{
+       setup_soup_server (test, NULL, NULL, got_server_connection, test);
+       client_connect (test, NULL, NULL, got_client_connection, test);
+       WAIT_UNTIL (test->server != NULL);
+       WAIT_UNTIL (test->client != NULL || test->client_error != NULL);
+       g_assert_no_error (test->client_error);
+}
+
+static void
+teardown_soup_connection (Test *test,
+                         gconstpointer data)
+{
+       g_clear_object (&test->client);
+       g_clear_error (&test->client_error);
+
+       if (test->session)
+               soup_test_session_abort_unref (test->session);
+
+       if (test->soup_server)
+               soup_test_server_quit_unref (test->soup_server);
+}
+
+
+static void
 on_text_message (SoupWebsocketConnection *ws,
                  SoupWebsocketDataType type,
                  GBytes *message,
@@ -213,6 +308,14 @@ on_close_set_flag (SoupWebsocketConnection *ws,
 }
 
 
+static void
+test_handshake (Test *test,
+                gconstpointer data)
+{
+       g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, SOUP_WEBSOCKET_STATE_OPEN);
+       g_assert_cmpint (soup_websocket_connection_get_state (test->server), ==, SOUP_WEBSOCKET_STATE_OPEN);
+}
+
 #define TEST_STRING "this is a test"
 
 static void
@@ -351,6 +454,20 @@ test_protocol_negotiate_direct (Test *test,
        g_object_unref (msg);
 }
 
+static void
+test_protocol_negotiate_soup (Test *test,
+                             gconstpointer unused)
+{
+       setup_soup_server (test, NULL, negotiate_server_protocols, got_server_connection, test);
+       client_connect (test, NULL, negotiate_client_protocols, got_client_connection, test);
+       WAIT_UNTIL (test->server != NULL);
+       WAIT_UNTIL (test->client != NULL || test->client_error != NULL);
+       g_assert_no_error (test->client_error);
+
+       g_assert_cmpstr (soup_websocket_connection_get_protocol (test->client), ==, negotiated_protocol);
+       g_assert_cmpstr (soup_websocket_connection_get_protocol (test->server), ==, negotiated_protocol);
+}
+
 static const char *mismatch_client_protocols[] = { "ddd", NULL };
 static const char *mismatch_server_protocols[] = { "aaa", "bbb", "ccc", NULL };
 
@@ -390,6 +507,17 @@ test_protocol_mismatch_direct (Test *test,
        g_object_unref (msg);
 }
 
+static void
+test_protocol_mismatch_soup (Test *test,
+                            gconstpointer unused)
+{
+       setup_soup_server (test, NULL, mismatch_server_protocols, got_server_connection, test);
+       client_connect (test, NULL, mismatch_client_protocols, got_client_connection, test);
+       WAIT_UNTIL (test->client_error != NULL);
+
+       g_assert_error (test->client_error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET);
+}
+
 static const char *all_protocols[] = { "aaa", "bbb", "ccc", NULL };
 
 static void
@@ -422,6 +550,21 @@ test_protocol_server_any_direct (Test *test,
 }
 
 static void
+test_protocol_server_any_soup (Test *test,
+                              gconstpointer unused)
+{
+       setup_soup_server (test, NULL, NULL, got_server_connection, test);
+       client_connect (test, NULL, all_protocols, got_client_connection, test);
+       WAIT_UNTIL (test->server != NULL);
+       WAIT_UNTIL (test->client != NULL || test->client_error != NULL);
+       g_assert_no_error (test->client_error);
+
+       g_assert_cmpstr (soup_websocket_connection_get_protocol (test->client), ==, NULL);
+       g_assert_cmpstr (soup_websocket_connection_get_protocol (test->server), ==, NULL);
+       g_assert_cmpstr (soup_message_headers_get_one (test->msg->response_headers, 
"Sec-WebSocket-Protocol"), ==, NULL);
+}
+
+static void
 test_protocol_client_any_direct (Test *test,
                                 gconstpointer unused)
 {
@@ -451,6 +594,21 @@ test_protocol_client_any_direct (Test *test,
 }
 
 static void
+test_protocol_client_any_soup (Test *test,
+                              gconstpointer unused)
+{
+       setup_soup_server (test, NULL, all_protocols, got_server_connection, test);
+       client_connect (test, NULL, NULL, got_client_connection, test);
+       WAIT_UNTIL (test->server != NULL);
+       WAIT_UNTIL (test->client != NULL || test->client_error != NULL);
+       g_assert_no_error (test->client_error);
+
+       g_assert_cmpstr (soup_websocket_connection_get_protocol (test->client), ==, NULL);
+       g_assert_cmpstr (soup_websocket_connection_get_protocol (test->server), ==, NULL);
+       g_assert_cmpstr (soup_message_headers_get_one (test->msg->response_headers, 
"Sec-WebSocket-Protocol"), ==, NULL);
+}
+
+static void
 test_close_clean_client (Test *test,
                          gconstpointer data)
 {
@@ -634,47 +792,103 @@ main (int argc,
 
        test_init (argc, argv, NULL);
 
+       g_test_add ("/websocket/soup/handshake", Test, NULL, 
+                   setup_soup_connection,
+                   test_handshake,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/send-client-to-server", Test, NULL,
                    setup_direct_connection,
                    test_send_client_to_server,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/send-client-to-server", Test, NULL, 
+                   setup_soup_connection,
+                   test_send_client_to_server,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/send-server-to-client", Test, NULL,
                    setup_direct_connection,
                    test_send_server_to_client,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/send-server-to-client", Test, NULL,
+                   setup_soup_connection,
+                   test_send_server_to_client,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/send-big-packets", Test, NULL,
                    setup_direct_connection,
                    test_send_big_packets,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/send-big-packets", Test, NULL,
+                   setup_soup_connection,
+                   test_send_big_packets,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/send-bad-data", Test, NULL,
                    setup_direct_connection,
                    test_send_bad_data,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/send-bad-data", Test, NULL,
+                   setup_soup_connection,
+                   test_send_bad_data,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/close-clean-client", Test, NULL,
                    setup_direct_connection,
                    test_close_clean_client,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/close-clean-client", Test, NULL,
+                   setup_soup_connection,
+                   test_close_clean_client,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/close-clean-server", Test, NULL,
                    setup_direct_connection,
                    test_close_clean_server,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/close-clean-server", Test, NULL,
+                   setup_soup_connection,
+                   test_close_clean_server,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/message-after-closing", Test, NULL,
                    setup_direct_connection,
                    test_message_after_closing,
                    teardown_direct_connection);
+       g_test_add ("/websocket/soup/message-after-closing", Test, NULL,
+                   setup_soup_connection,
+                   test_message_after_closing,
+                   teardown_soup_connection);
+
 
        g_test_add ("/websocket/direct/protocol-negotiate", Test, NULL, NULL,
                    test_protocol_negotiate_direct,
                    NULL);
+       g_test_add ("/websocket/soup/protocol-negotiate", Test, NULL, NULL,
+                   test_protocol_negotiate_soup,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/protocol-mismatch", Test, NULL, NULL,
                    test_protocol_mismatch_direct,
                    NULL);
+       g_test_add ("/websocket/soup/protocol-mismatch", Test, NULL, NULL,
+                   test_protocol_mismatch_soup,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/protocol-server-any", Test, NULL, NULL,
                    test_protocol_server_any_direct,
                    NULL);
+       g_test_add ("/websocket/soup/protocol-server-any", Test, NULL, NULL,
+                   test_protocol_server_any_soup,
+                   teardown_soup_connection);
+
        g_test_add ("/websocket/direct/protocol-client-any", Test, NULL, NULL,
                    test_protocol_client_any_direct,
                    NULL);
+       g_test_add ("/websocket/soup/protocol-client-any", Test, NULL, NULL,
+                   test_protocol_client_any_soup,
+                   teardown_soup_connection);
+
 
        g_test_add ("/websocket/direct/receive-fragmented", Test, NULL,
                    setup_half_direct_connection,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]