[libsoup/wip/hadess/reverse-http: 1/3] Add reverse-HTTP client support



commit 1225d16abaa393c3bf2ea6bc130edaeca1716104
Author: Bastien Nocera <hadess hadess net>
Date:   Tue Jun 16 14:18:27 2015 +0200

    Add reverse-HTTP client support
    
    This allows an HTTP client (SoupSession) to become an HTTP server
    (SoupServer).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=637387

 libsoup/soup-message-private.h   |   6 ++
 libsoup/soup-message-server-io.c |   5 +-
 libsoup/soup-message.c           |  18 ++++
 libsoup/soup-misc-private.h      |   5 +
 libsoup/soup-server.c            |  28 +++++-
 libsoup/soup-session.c           | 203 +++++++++++++++++++++++++++++++++++++++
 libsoup/soup-session.h           |  21 ++++
 7 files changed, 283 insertions(+), 3 deletions(-)
---
diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h
index 2f3d58a7..046f60f2 100644
--- a/libsoup/soup-message-private.h
+++ b/libsoup/soup-message-private.h
@@ -43,6 +43,8 @@ typedef struct {
        SoupRequest       *request;
 
        SoupMessagePriority priority;
+
+       gboolean           is_reverse_http;
 } SoupMessagePrivate;
 #define SOUP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_MESSAGE, 
SoupMessagePrivate))
 
@@ -145,6 +147,10 @@ gboolean soup_message_disables_feature (SoupMessage *msg,
 void soup_message_set_https_status (SoupMessage    *msg,
                                    SoupConnection *conn);
 
+void soup_message_set_is_reverse_http (SoupMessage *msg,
+                                      gboolean     is_reverse_http);
+gboolean soup_message_get_is_reverse_http (SoupMessage *msg);
+
 void soup_message_network_event (SoupMessage         *msg,
                                 GSocketClientEvent   event,
                                 GIOStream           *connection);
diff --git a/libsoup/soup-message-server-io.c b/libsoup/soup-message-server-io.c
index 35b544c2..cd2e7c17 100644
--- a/libsoup/soup-message-server-io.c
+++ b/libsoup/soup-message-server-io.c
@@ -103,13 +103,14 @@ parse_request_headers (SoupMessage *msg, char *headers, guint headers_len,
        } else if (*req_path != '/') {
                /* Absolute URI */
                uri = soup_uri_new (req_path);
-       } else if (req_host) {
+       } else if (req_host && *req_host != '\0') {
                url = g_strdup_printf ("%s://%s%s",
                                       soup_socket_is_ssl (sock) ? "https" : "http",
                                       req_host, req_path);
                uri = soup_uri_new (url);
                g_free (url);
-       } else if (priv->http_version == SOUP_HTTP_1_0) {
+       } else if (priv->http_version == SOUP_HTTP_1_0 ||
+                  soup_message_get_is_reverse_http (msg)) {
                /* No Host header, no AbsoluteUri */
                SoupAddress *addr = soup_socket_get_local_address (sock);
 
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index e4d78476..60375396 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -1918,6 +1918,24 @@ soup_message_set_https_status (SoupMessage *msg, SoupConnection *conn)
        }
 }
 
+void
+soup_message_set_is_reverse_http (SoupMessage *msg, gboolean is_reverse_http)
+{
+       SoupMessagePrivate *priv;
+
+       priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+       priv->is_reverse_http = is_reverse_http;
+}
+
+gboolean
+soup_message_get_is_reverse_http (SoupMessage *msg)
+{
+       SoupMessagePrivate *priv;
+
+       priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+       return priv->is_reverse_http;
+}
+
 /**
  * soup_message_get_https_status:
  * @msg: a #SoupMessage
diff --git a/libsoup/soup-misc-private.h b/libsoup/soup-misc-private.h
index d8416f23..f98bacb3 100644
--- a/libsoup/soup-misc-private.h
+++ b/libsoup/soup-misc-private.h
@@ -46,4 +46,9 @@ SoupAddress *soup_address_new_from_gsockaddr (GSocketAddress *addr);
 gboolean           soup_host_matches_host    (const gchar *host,
                                              const gchar *compare_with);
 
+SoupServer *soup_server_new_reverse_http (GIOStream       *stream,
+                                         GSocketAddress  *local_addr,
+                                         GSocketAddress  *remote_addr,
+                                         GError         **error);
+
 #endif /* __SOUP_MISC_PRIVATE_H__ */
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c
index 58d9574f..99af63e5 100644
--- a/libsoup/soup-server.c
+++ b/libsoup/soup-server.c
@@ -183,6 +183,8 @@ typedef struct {
        SoupAddress       *legacy_iface;
        int                legacy_port;
 
+       gboolean           is_reverse_http;
+
        gboolean           disposed;
 
 } SoupServerPrivate;
@@ -1204,7 +1206,7 @@ request_finished (SoupMessage *msg, SoupMessageIOCompletion completion, gpointer
        if (completion == SOUP_MESSAGE_IO_COMPLETE &&
            soup_socket_is_connected (sock) &&
            soup_message_is_keepalive (msg) &&
-           priv->listeners) {
+           (priv->listeners || priv->is_reverse_http)) {
                start_request (server, client);
        } else {
                soup_socket_disconnect (client->sock);
@@ -1424,6 +1426,7 @@ start_request (SoupServer *server, SoupClientContext *client)
        msg = g_object_new (SOUP_TYPE_MESSAGE,
                            SOUP_MESSAGE_SERVER_SIDE, TRUE,
                            NULL);
+       soup_message_set_is_reverse_http (msg, priv->is_reverse_http);
        client->msg = msg;
 
        if (priv->server_header) {
@@ -1524,6 +1527,29 @@ new_connection (SoupSocket *listener, SoupSocket *sock, gpointer user_data)
        soup_server_accept_socket (server, sock);
 }
 
+SoupServer *
+soup_server_new_reverse_http (GIOStream       *stream,
+                             GSocketAddress  *local_addr,
+                             GSocketAddress  *remote_addr,
+                             GError         **error)
+{
+       SoupServer *server;
+       SoupServerPrivate *priv;
+
+       g_return_val_if_fail (local_addr, NULL);
+       g_return_val_if_fail (remote_addr, NULL);
+
+       server = soup_server_new (NULL, NULL);
+       priv = soup_server_get_instance_private (server);
+       priv->is_reverse_http = TRUE;
+       if (!soup_server_accept_iostream (server, stream, local_addr, remote_addr, error)) {
+               g_object_unref (server);
+               return NULL;
+       }
+
+       return server;
+}
+
 /**
  * soup_server_run_async:
  * @server: a #SoupServer
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index ae340b9c..388204a1 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -5062,3 +5062,206 @@ soup_session_connect_finish (SoupSession  *session,
 
         return g_task_propagate_pointer (G_TASK (result), error);
 }
+
+/**
+ * SOUP_REVERSE_HTTP_ERROR:
+ *
+ * A #GError domain for reverse-HTTP-related errors. Used with
+ * #SoupReverseHTTPError.
+ *
+ * Since: 2.54
+ */
+/**
+ * SoupReverseHTTPError:
+ * @SOUP_REVERSE_HTTP_ERROR_NOT_REVERSE_HTTP: attempted to handshake with a
+ *   server that does not appear to understand reverse-HTTP.
+ * @SOUP_REVERSE_HTTP_ERROR_BAD_HANDSHAKE: the reverse-HTTP handshake failed
+ *   because some detail was invalid.
+ *
+ * reverse-HTTP-related errors.
+ *
+ * Since: 2.54
+ */
+
+GQuark
+soup_reverse_http_error_quark (void)
+{
+       static GQuark error;
+       if (!error)
+               return g_quark_from_static_string ("reverse-http-error-quark");
+       return error;
+}
+
+static void reverse_http_connect_async_stop (SoupMessage *msg, gpointer user_data);
+
+static void
+reverse_http_connect_async_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+       GTask *task = user_data;
+
+       g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
+                                             0, 0, NULL, NULL, task);
+
+       g_task_return_new_error (task,
+                                SOUP_REVERSE_HTTP_ERROR, SOUP_REVERSE_HTTP_ERROR_NOT_REVERSE_HTTP,
+                                "%s", _("The server did not accept the Reverse HTTP handshake."));
+       g_object_unref (task);
+}
+
+static gboolean
+soup_reverse_http_client_verify_handshake (SoupMessage  *msg,
+                                          GError      **error)
+{
+       if (msg->status_code == SOUP_STATUS_BAD_REQUEST) {
+               g_set_error_literal (error,
+                                    SOUP_REVERSE_HTTP_ERROR,
+                                    SOUP_REVERSE_HTTP_ERROR_BAD_HANDSHAKE,
+                                    _("Server rejected reverse-HTTP handshake"));
+               return FALSE;
+       }
+
+       if (msg->status_code != SOUP_STATUS_SWITCHING_PROTOCOLS) {
+               g_set_error_literal (error,
+                                    SOUP_REVERSE_HTTP_ERROR,
+                                    SOUP_REVERSE_HTTP_ERROR_BAD_HANDSHAKE,
+                                    _("Server ignored reverse-HTTP handshake"));
+               return FALSE;
+       }
+
+       if (!soup_message_headers_header_equals (msg->response_headers, "Upgrade", "PTTH/1.0")) {
+               g_set_error_literal (error,
+                                    SOUP_REVERSE_HTTP_ERROR,
+                                    SOUP_REVERSE_HTTP_ERROR_BAD_HANDSHAKE,
+                                    _("Server ignored reverse-HTTP handshake"));
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+reverse_http_connect_async_stop (SoupMessage *msg, gpointer user_data)
+{
+       GTask *task = user_data;
+       SoupMessageQueueItem *item = g_task_get_task_data (task);
+       GError *error = NULL;
+
+       g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
+                                             0, 0, NULL, NULL, task);
+
+       if (soup_reverse_http_client_verify_handshake (item->msg, &error)) {
+               SoupServer *server;
+               GIOStream *stream;
+               GSocket *socket;
+               GSocketAddress *local_addr, *remote_addr;
+
+               stream = soup_session_steal_connection (item->session, item->msg);
+               socket = g_object_get_data (G_OBJECT (stream), "GSocket");
+               local_addr = g_socket_get_local_address (socket, NULL);
+               remote_addr = g_socket_get_remote_address (socket, NULL);
+
+               if (!local_addr || !remote_addr) {
+                       g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED,
+                                                _("Local or remote address not connected"));
+                       return;
+               } else {
+                       server = soup_server_new_reverse_http (stream, local_addr, remote_addr, &error);
+                       if (!server)
+                               g_task_return_error (task, error);
+                       else
+                               g_task_return_pointer (task, server, g_object_unref);
+               }
+
+               g_clear_object (&local_addr);
+               g_clear_object (&remote_addr);
+       } else
+               g_task_return_error (task, error);
+       g_object_unref (task);
+}
+
+/**
+ * soup_session_reverse_http_connect_async:
+ * @session: a #SoupSession
+ * @msg: #SoupMessage indicating the reverse HTTP server to connect to
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously creates a #SoupServer to communicate
+ * with a remote server. The path and method to use when creating the
+ * #SoupMessage are specific to the application you're connecting to.
+ *
+ * All necessary reverse-HTTP-related headers will be added to @msg, and
+ * it will then be sent and asynchronously processed normally.
+ *
+ * If the server returns "101 Switching Protocols", then @msg's status
+ * code and response headers will be updated, and then the reverse HTTP
+ * handshake will be completed. On success,
+ * soup_reverse_http_connect_finish() will return a new
+ * #SoupServer. 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_reverse_http_connect_finish() will return
+ * %SOUP_REVERSE_HTTP_ERROR_NOT_REVERSE_HTTP.
+ *
+ * The <ulink
+ * url="http://tools.ietf.org/html/draft-lentczner-rhttp-00";>Reverse HTTP draft RFC</ulink>
+ * contains more details about the mechanism.
+ *
+ * Since: 2.54
+ */
+void
+soup_session_reverse_http_connect_async (SoupSession          *session,
+                                        SoupMessage          *msg,
+                                        GCancellable         *cancellable,
+                                        GAsyncReadyCallback   callback,
+                                        gpointer              user_data)
+{
+       SoupSessionPrivate *priv;
+       SoupMessageQueueItem *item;
+       GTask *task;
+
+       g_return_if_fail (SOUP_IS_SESSION (session));
+       g_return_if_fail (SOUP_IS_MESSAGE (msg));
+
+       priv = soup_session_get_instance_private (session);
+       g_return_if_fail (priv->use_thread_context);
+
+       soup_message_headers_replace (msg->request_headers, "Upgrade", "PTTH/1.0");
+       soup_message_headers_append (msg->request_headers, "Connection", "Upgrade");
+
+       task = g_task_new (session, cancellable, callback, user_data);
+       item = soup_session_append_queue_item (session, msg, TRUE, FALSE,
+                                              reverse_http_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 (reverse_http_connect_async_stop), task);
+       soup_session_kick_queue (session);
+}
+
+/**
+ * soup_session_reverse_http_connect_finish:
+ * Gets the response to a
+ * soup_session_reverse_http_connect_async() call and (if successful),
+ * returns a SoupServer that can be used to communicate
+ * with the server (which will now be the client).
+ *
+ * Return value: (transfer full): a new #SoupServer, or
+ *   %NULL on error.
+ *
+ * Since: 2.54
+ */
+SoupServer *
+soup_session_reverse_http_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 46e4e154..c05463f4 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -252,6 +252,27 @@ GIOStream *soup_session_connect_finish (SoupSession                       *sessi
                                        GAsyncResult                      *result,
                                        GError                           **error);
 
+SOUP_AVAILABLE_IN_2_66
+GQuark soup_reverse_http_error_quark (void);
+#define SOUP_REVERSE_HTTP_ERROR soup_reverse_http_error_quark ()
+
+typedef enum {
+       SOUP_REVERSE_HTTP_ERROR_NOT_REVERSE_HTTP,
+       SOUP_REVERSE_HTTP_ERROR_BAD_HANDSHAKE
+} SoupReverseHTTPError;
+
+SOUP_AVAILABLE_IN_2_66
+void                     soup_session_reverse_http_connect_async  (SoupSession          *session,
+                                                                  SoupMessage          *msg,
+                                                                  GCancellable         *cancellable,
+                                                                  GAsyncReadyCallback   callback,
+                                                                  gpointer              user_data);
+
+SOUP_AVAILABLE_IN_2_66
+SoupServer              *soup_session_reverse_http_connect_finish (SoupSession      *session,
+                                                                  GAsyncResult     *result,
+                                                                  GError          **error);
+
 G_END_DECLS
 
 #endif /* __SOUP_SESSION_H__ */


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