summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@gnome.org>2014-11-30 10:26:23 -0500
committerDan Winship <danw@gnome.org>2015-02-27 17:59:04 -0500
commita08942df00a8115901f19593734a3d2b1efbdb3e (patch)
tree5781d2b2274570f5f3c55074cb1ae35e9af3cb3a
parent53ac6213a8aa886c5ace1dc0ba8268f245aa0f3b (diff)
downloadlibsoup-websocket.tar.gz
websockets: add WebSocket support to SoupSession and SoupServerwebsocket
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.
-rw-r--r--libsoup/libsoup-2.4.sym3
-rw-r--r--libsoup/soup-server.c266
-rw-r--r--libsoup/soup-server.h15
-rw-r--r--libsoup/soup-session.c131
-rw-r--r--libsoup/soup-session.h15
-rw-r--r--tests/websocket-test.c214
6 files changed, 583 insertions, 61 deletions
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index 4f52b842..69d46efb 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 6a9f0c88..05f4de29 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 3cabc037..36a94907 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 c12c1513..4c768ac0 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 5dcd747c..4c64ce43 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 7e6ae05f..c3aa1427 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,