[libsoup/wip/hadess/reverse-http: 1/3] Add reverse-HTTP client support
- From: Bastien Nocera <hadess src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsoup/wip/hadess/reverse-http: 1/3] Add reverse-HTTP client support
- Date: Sun, 21 Oct 2018 12:43:59 +0000 (UTC)
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]