[libsoup/wip/http2] Add initial HTTP2 backend




commit a9253628f9f45966f90071adcd8ea9b54d580b4a
Author: Patrick Griffis <pgriffis igalia com>
Date:   Sun May 2 13:20:12 2021 -0500

    Add initial HTTP2 backend

 docs/reference/meson.build                   |    2 +
 libsoup/http2/soup-client-message-io-http2.c | 1386 ++++++++++++++++++++++++++
 libsoup/http2/soup-client-message-io-http2.h |   14 +
 libsoup/http2/soup-memory-input-stream.c     |  611 ++++++++++++
 libsoup/http2/soup-memory-input-stream.h     |   16 +
 libsoup/meson.build                          |    5 +
 libsoup/soup-client-message-io-http1.c       |   70 +-
 libsoup/soup-client-message-io.c             |   61 +-
 libsoup/soup-client-message-io.h             |   48 +-
 libsoup/soup-connection.c                    |  132 ++-
 libsoup/soup-connection.h                    |    2 +
 libsoup/soup-logger-private.h                |    2 +
 libsoup/soup-logger.c                        |   20 +-
 libsoup/soup-message.c                       |   21 +-
 libsoup/soup-message.h                       |    3 +-
 libsoup/soup-misc.c                          |   16 +
 libsoup/soup-misc.h                          |    2 +
 libsoup/soup-session.c                       |   93 +-
 libsoup/soup-status.c                        |    1 +
 libsoup/soup-status.h                        |    1 +
 meson.build                                  |   18 +-
 tests/http2-server.py                        |   94 ++
 tests/http2-test.c                           |  531 ++++++++++
 tests/memory-stream-test.c                   |  101 ++
 tests/meson.build                            |    7 +
 tests/test-utils.c                           |   71 ++
 tests/test-utils.h                           |    3 +
 27 files changed, 3229 insertions(+), 102 deletions(-)
---
diff --git a/docs/reference/meson.build b/docs/reference/meson.build
index 3aea8764..22c9042a 100644
--- a/docs/reference/meson.build
+++ b/docs/reference/meson.build
@@ -41,6 +41,8 @@ ignore_headers = [
   'soup-client-message-io.h',
   'soup-message-io-completion.h',
   'soup-client-message-io-http1.h',
+  'soup-client-message-io-http2.h',
+  'soup-memory-input-stream.h',
 ]
 
 mkdb_args = [
diff --git a/libsoup/http2/soup-client-message-io-http2.c b/libsoup/http2/soup-client-message-io-http2.c
new file mode 100644
index 00000000..1baaa3e8
--- /dev/null
+++ b/libsoup/http2/soup-client-message-io-http2.c
@@ -0,0 +1,1386 @@
+/* soup-message-io-http2.c
+ *
+ * Copyright 2021 Igalia S.L.
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "libsoup-http2"
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "soup-client-message-io-http2.h"
+
+#include "soup-body-input-stream.h"
+#include "soup-message-metrics-private.h"
+#include "soup-message-private.h"
+#include "soup-message-io-source.h"
+#include "soup-message-queue-item.h"
+#include "content-sniffer/soup-content-sniffer-stream.h"
+#include "soup-client-input-stream.h"
+#include "soup-logger-private.h"
+#include "soup-uri-utils-private.h"
+
+#include "content-decoder/soup-content-decoder.h"
+#include "soup-memory-input-stream.h"
+
+#include <nghttp2/nghttp2.h>
+
+#define FRAME_HEADER_SIZE 9
+
+typedef enum {
+        STATE_NONE,
+        STATE_WRITE_HEADERS,
+        STATE_WRITE_DATA,
+        STATE_WRITE_DONE,
+        STATE_READ_HEADERS,
+        STATE_READ_DATA,
+        STATE_READ_DATA_SNIFFED,
+        STATE_READ_DONE,
+        STATE_ERROR,
+} SoupHTTP2IOState;
+
+typedef struct {
+        SoupClientMessageIO iface;
+
+        GIOStream *stream;
+        GInputStream *istream;
+        GOutputStream *ostream;
+
+        GMainContext *async_context;
+
+        GPtrArray *messages;
+        GHashTable *message_errors;
+
+        nghttp2_session *session;
+
+        // Owned by nghttp2
+        guint8 *write_buffer;
+        gssize write_buffer_size;
+        gssize written_bytes;
+
+        gboolean is_shutdown;
+} SoupMessageIOHTTP2;
+
+typedef struct {
+        SoupMessageQueueItem *item;
+        SoupMessage *msg;
+        SoupMessageMetrics *metrics;
+        GCancellable *cancellable;
+        GInputStream *decoded_data_istream;
+        GInputStream *memory_data_istream;
+
+        // Request body logger
+        SoupLogger *logger;
+
+        // Both data sources
+        GCancellable *data_source_cancellable;
+
+        // Pollable data sources
+        GSource *data_source_poll;
+
+        // Non-pollable data sources
+        GByteArray *data_source_buffer;
+        GError *data_source_error;
+        gboolean data_source_eof;
+
+        GSource *io_source;
+        SoupMessageIOHTTP2 *io; // Unowned
+        SoupMessageIOCompletionFn completion_cb;
+        gpointer completion_data;
+        SoupHTTP2IOState state;
+        gboolean paused;
+        guint32 stream_id;
+} SoupHTTP2MessageData;
+
+static void soup_message_io_http2_finished (SoupClientMessageIO *, SoupMessage *);
+static gboolean io_read_or_write (SoupMessageIOHTTP2 *, gboolean, GCancellable *, GError **);
+
+#if 0
+static SoupHTTP2MessageData *
+get_message_by_stream_id (SoupMessageIOHTTP2 *io, guint32 stream_id)
+{
+        const guint len = io->messages->len;
+
+        for (uint i = 0; i < len; ++i) {
+                SoupHTTP2MessageData *data = io->messages->pdata[i];
+                if (data->stream_id == stream_id)
+                        return data;
+        }
+
+        if (stream_id != 0)
+                g_warning ("Recieved frame for unknown stream id %u!", stream_id);
+        return NULL;
+}
+#endif
+
+static const char *
+frame_type_to_string (nghttp2_frame_type type)
+{
+        switch (type) {
+        case NGHTTP2_DATA:
+                return "DATA";
+        case NGHTTP2_HEADERS:
+                return "HEADERS";
+        case NGHTTP2_PRIORITY:
+                return "PRIORITY";
+        case NGHTTP2_RST_STREAM:
+                return "RST_STREAM";
+        case NGHTTP2_SETTINGS:
+                return "SETTINGS";
+        case NGHTTP2_PUSH_PROMISE:
+                return "PUSH_PROMISE";
+        case NGHTTP2_PING:
+                return "PING";
+        case NGHTTP2_GOAWAY:
+                return "GOAWAY";
+        case NGHTTP2_WINDOW_UPDATE:
+                return "WINDOW_UPDATE";
+        case NGHTTP2_CONTINUATION:
+                return "CONTINUATION";
+        case NGHTTP2_ALTSVC:
+                return "ALTSVC";
+        case NGHTTP2_ORIGIN:
+                return "ORIGIN";
+        default:
+                g_warn_if_reached ();
+                return "UNKNOWN";
+        }
+}
+
+static void
+h2_debug (SoupMessageIOHTTP2 *io, SoupHTTP2MessageData *data, const char *format, ...)
+{
+        va_list args;
+        char *message;
+        guint32 stream_id = 0;
+
+       va_start (args, format);
+       message = g_strdup_vprintf (format, args);
+       va_end (args);
+
+        if (data)
+                stream_id = data->stream_id;
+
+        g_assert (io);
+        g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "[C%p-S%u] %s", io->stream, stream_id, message);
+
+        g_free (message);
+}
+
+static SoupMessageIOHTTP2 *
+get_io_data (SoupMessage *msg)
+{
+        return (SoupMessageIOHTTP2 *)soup_message_get_io_data (msg);
+}
+
+static int
+get_data_io_priority (SoupHTTP2MessageData *data)
+{
+       if (!data->item->task)
+               return G_PRIORITY_DEFAULT;
+
+       return g_task_get_priority (data->item->task);
+}
+
+static void
+set_error_for_data (SoupMessageIOHTTP2 *io, SoupHTTP2MessageData *data, GError *error)
+{
+        g_debug ("set_error_for_data: %s", error->message);
+        g_hash_table_replace (io->message_errors, data, error);
+}
+
+static GError *
+get_error_for_data (SoupMessageIOHTTP2 *io, SoupHTTP2MessageData *data)
+{
+        GError *error = NULL;
+
+        g_hash_table_steal_extended (io->message_errors, data, NULL, (gpointer*)&error);
+
+        return error;
+}
+
+/* HTTP2 read callbacks */
+
+static int
+on_header_callback (nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t 
namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data)
+{
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
+
+        if (!data)
+                return 0;
+
+        SoupMessage *msg = data->msg;
+        if (name[0] == ':') {
+                if (strcmp ((char *)name, ":status") == 0) {
+                        guint status_code = (guint)g_ascii_strtoull ((char *)value, NULL, 10);
+                        soup_message_set_status (msg, status_code, NULL);
+                        return 0;
+                }
+                g_debug ("%s = %s", name, value);
+                return 0;
+        }
+
+        // FIXME: Encoding
+        char *name_utf8 = g_utf8_make_valid ((const char *)name, namelen);
+        char *value_utf8 = g_utf8_make_valid ((const char *)value, valuelen);
+        soup_message_headers_append (soup_message_get_response_headers (data->msg),
+                                     name_utf8, value_utf8);
+        g_free (name_utf8);
+        g_free (value_utf8);
+        return 0;
+}
+
+static GError *
+memory_stream_want_read_callback (SoupMemoryInputStream *stream, GCancellable *cancellable, gboolean 
blocking, gpointer user_data)
+{
+        SoupHTTP2MessageData *data = (SoupHTTP2MessageData*)user_data;
+        GError *error = NULL;
+
+        g_debug ("memory_stream_want_read_callback write=%d read=%d", nghttp2_session_want_write 
(data->io->session), nghttp2_session_want_read (data->io->session));
+
+        io_read_or_write (data->io, blocking, cancellable, &error);
+
+        return error;
+}
+
+static int
+on_begin_frame_callback (nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data)
+{
+        // SoupMessageIOHTTP2 *io = user_data;
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, hd->stream_id);
+
+        h2_debug (user_data, data, "[RECV] [%s] Beginning", frame_type_to_string (hd->type));
+
+        if (!data)
+                return 0;
+
+        switch (hd->type) {
+        case NGHTTP2_HEADERS:
+                if (data->state < STATE_READ_HEADERS)
+                        data->state = STATE_READ_HEADERS;
+                break;
+        case NGHTTP2_DATA: {
+                // We may have sniffed a previous DATA frame
+                if (data->state < STATE_READ_DATA)
+                        data->state = STATE_READ_DATA;
+                if (!data->memory_data_istream) {
+                        data->memory_data_istream = soup_memory_input_stream_new (G_POLLABLE_INPUT_STREAM 
(data->io->istream));
+                        g_signal_connect (data->memory_data_istream, "want-read",
+                                          G_CALLBACK (memory_stream_want_read_callback), data);
+                }
+                if (!data->decoded_data_istream)
+                        data->decoded_data_istream = soup_message_setup_body_istream 
(data->memory_data_istream, data->msg,
+                                                                                      data->item->session, 
SOUP_STAGE_MESSAGE_BODY);
+                break;
+        }
+        }
+
+        return 0;
+}
+
+static void
+handle_goaway (SoupMessageIOHTTP2 *io, guint32 error_code, guint32 last_stream_id)
+{
+        const guint len = io->messages->len;
+
+        for (uint i = 0; i < len; ++i) {
+                SoupHTTP2MessageData *data = io->messages->pdata[i];
+                /* If there is no error it is a graceful shutdown and
+                 * existing messages can be handled otherwise it is a fatal error */
+                if ((error_code == 0 && data->stream_id > last_stream_id) ||
+                     data->state < STATE_READ_DONE) {
+                        h2_debug (io, data, "[GOAWAY] Error: %s", nghttp2_http2_strerror (error_code));
+                        data->state = STATE_ERROR;
+                        // TODO: We can restart unfinished messages
+                        set_error_for_data (io, data, g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                            "HTTP/2 Error: %s", nghttp2_http2_strerror (error_code)));
+                }
+        }
+}
+
+static int
+on_frame_recv_callback (nghttp2_session *session, const nghttp2_frame *frame, gpointer user_data)
+{
+        SoupMessageIOHTTP2 *io = user_data;
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
+
+        h2_debug (io, data, "[RECV] [%s] Recieved (%u)", frame_type_to_string (frame->hd.type), 
frame->hd.flags);
+
+        if (frame->hd.type == NGHTTP2_GOAWAY) {
+                handle_goaway (io, frame->goaway.error_code, frame->goaway.last_stream_id);
+                io->is_shutdown = TRUE;
+                return 0;
+        }
+
+        if (!data) {
+                if (frame->hd.stream_id != 0 && frame->hd.flags != NGHTTP2_FLAG_END_STREAM)
+                        g_warn_if_reached ();
+                return 0;
+        }
+
+        switch (frame->hd.type) {
+        case NGHTTP2_HEADERS:
+                if (data->metrics)
+                        data->metrics->response_header_bytes_received += frame->hd.length + 
FRAME_HEADER_SIZE;
+
+                if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && frame->hd.flags & 
NGHTTP2_FLAG_END_HEADERS) {
+                        h2_debug (io, data, "[HEADERS] status %u", soup_message_get_status (data->msg));
+                        if (SOUP_STATUS_IS_INFORMATIONAL (soup_message_get_status (data->msg))) {
+                                soup_message_got_informational (data->msg);
+                                soup_message_cleanup_response (data->msg);
+                                soup_memory_input_stream_complete (SOUP_MEMORY_INPUT_STREAM 
(data->memory_data_istream));
+                                data->state = STATE_READ_DONE;
+                                break;
+                        } else if (soup_message_get_status (data->msg) == SOUP_STATUS_NO_CONTENT) {
+                                data->state = STATE_READ_DONE;
+                        }
+                        soup_message_got_headers (data->msg);
+                }
+                break;
+        case NGHTTP2_DATA:
+                if (data->metrics)
+                        data->metrics->response_body_bytes_received += frame->data.hd.length + 
FRAME_HEADER_SIZE;
+                break;
+        case NGHTTP2_RST_STREAM:
+                h2_debug (io, data, "[RST_STREAM] %s", nghttp2_http2_strerror 
(frame->rst_stream.error_code));
+                if (frame->rst_stream.error_code != NGHTTP2_NO_ERROR) {
+                        set_error_for_data (io, data, g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+                                                                           nghttp2_http2_strerror 
(frame->rst_stream.error_code)));
+                        data->state = STATE_ERROR;
+                }
+                break;
+        };
+
+        if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+                h2_debug (io, data, "Stream done");
+                data->state = STATE_READ_DONE;
+                if (frame->hd.type == NGHTTP2_DATA) {
+                        soup_memory_input_stream_complete (SOUP_MEMORY_INPUT_STREAM 
(data->memory_data_istream));
+                        soup_message_got_body (data->msg);
+                }
+
+                // soup_message_io_http2_finished (data->msg);
+                // nghttp2_submit_rst_stream (session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, 
NGHTTP2_STREAM_CLOSED);
+        }
+
+        return 0;
+}
+
+static int
+on_data_chunk_recv_callback (nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t 
*data, size_t len, void *user_data)
+{
+        SoupMessageIOHTTP2 *io = user_data;
+        SoupHTTP2MessageData *msgdata = nghttp2_session_get_stream_user_data (session, stream_id);
+
+        if (!msgdata)
+                return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+        h2_debug (io, msgdata, "[DATA] Recieved chunk, len=%zu, flags=%u, paused=%d", len, flags, 
msgdata->paused);
+
+        if (msgdata->paused)
+                return NGHTTP2_ERR_PAUSE;
+
+        SoupMessage *msg = msgdata->msg;
+        GBytes *bytes = g_bytes_new (data, len);
+        g_assert (msgdata->memory_data_istream != NULL);
+        soup_memory_input_stream_add_bytes (SOUP_MEMORY_INPUT_STREAM (msgdata->memory_data_istream), bytes);
+        g_bytes_unref (bytes);
+
+        if (msgdata->state < STATE_READ_DATA_SNIFFED) {
+                if (soup_message_get_content_sniffer (msg)) {
+                        SoupContentSnifferStream *sniffer_stream = SOUP_CONTENT_SNIFFER_STREAM 
(msgdata->decoded_data_istream);
+                        GError *error = NULL;
+                        if (soup_content_sniffer_stream_is_ready (sniffer_stream, FALSE, NULL, &error)) {
+                                GHashTable *params;
+                                const char *content_type = soup_content_sniffer_stream_sniff 
(sniffer_stream, &params);
+
+                                msgdata->state = STATE_READ_DATA_SNIFFED;
+                                soup_message_content_sniffed (msg, content_type, params);
+                                h2_debug (io, msgdata, "[DATA] Sniffed %s", content_type);
+                        } else {
+                                h2_debug (io, msgdata, "[DATA] Sniffer stream was not ready %s", 
error->message);
+                                g_clear_error (&error);
+                        }
+                }
+                else
+                        msgdata->state = STATE_READ_DATA_SNIFFED;
+        }
+
+        return 0;
+}
+
+/* HTTP2 write callbacks */
+
+static int
+on_before_frame_send_callback (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+{
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
+
+        // h2_debug (user_data, data, "[SEND] [%s] Before", frame_type_to_string (frame->hd.type));
+
+        if (!data)
+                return 0;
+
+        switch (frame->hd.type) {
+        case NGHTTP2_HEADERS:
+                data->state = STATE_WRITE_HEADERS;
+                break;
+        case NGHTTP2_DATA:
+                data->state = STATE_WRITE_DATA;
+                break;
+        }
+
+        return 0;
+}
+
+static int
+on_frame_send_callback (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+{
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
+
+        if (!data) {
+                h2_debug (user_data, NULL, "[SEND] [%s]", frame_type_to_string (frame->hd.type));
+                return 0;
+        }
+
+        switch (frame->hd.type) {
+        case NGHTTP2_HEADERS:
+                h2_debug (user_data, data, "[SEND] [HEADERS] finished=%d",
+                          (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) ? 1 : 0);
+                if (data->metrics)
+                        data->metrics->request_header_bytes_sent += frame->hd.length + FRAME_HEADER_SIZE;
+
+                if (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) {
+                        soup_message_wrote_headers (data->msg);
+                        if (soup_message_get_request_body_stream (data->msg) == NULL) {
+                                data->state = STATE_WRITE_DONE;
+                                soup_message_wrote_body (data->msg);
+                        }
+                }
+                break;
+        case NGHTTP2_DATA:
+                h2_debug (user_data, data, "[SEND] [DATA] bytes=%zu, finished=%d",
+                          frame->data.hd.length, frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
+                if (data->metrics) {
+                        data->metrics->request_body_bytes_sent += frame->hd.length + FRAME_HEADER_SIZE;
+                        data->metrics->request_body_size += frame->data.hd.length;
+                }
+                if (frame->data.hd.length)
+                        soup_message_wrote_body_data (data->msg, frame->data.hd.length);
+                if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+                        data->state = STATE_WRITE_DONE;
+                        soup_message_wrote_body (data->msg);
+                }
+                break;
+        default:
+                h2_debug (user_data, NULL, "[SEND] [%s]", frame_type_to_string (frame->hd.type));
+                break;
+        }
+
+        return 0;
+}
+
+static int
+on_frame_not_send_callback (nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void 
*user_data)
+{
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
+
+        h2_debug (user_data, data, "[SEND] [%s] Failed", frame_type_to_string (frame->hd.type));
+
+        if (!data)
+                return 0;
+
+        data->state = STATE_ERROR;
+
+        return 0;
+}
+
+static int
+on_stream_close_callback (nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data)
+{
+        g_debug ("on_stream_close %d", stream_id);
+        return 0;
+}
+
+static gboolean
+on_data_readable (GInputStream *stream, gpointer user_data)
+{
+        SoupHTTP2MessageData *data = (SoupHTTP2MessageData*)user_data;
+
+        nghttp2_session_resume_data (data->io->session, data->stream_id);
+
+        g_clear_pointer (&data->data_source_poll, g_source_unref);
+        return G_SOURCE_REMOVE;
+}
+
+static void
+on_data_read (GInputStream *source, GAsyncResult *res, gpointer user_data)
+{
+        SoupHTTP2MessageData *data = user_data;
+        GError *error = NULL;
+        gssize read = g_input_stream_read_finish (source, res, &error);
+
+        g_debug ("[SEND_BODY] Read %zu", read);
+
+        // This operation may have outlived the message data in which
+        // case this will have been cancelled.
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                g_error_free (error);
+                return;
+        }
+
+        if (read < 0) {
+                g_byte_array_set_size (data->data_source_buffer, 0);
+                data->data_source_error = g_steal_pointer (&error);
+        } else if (read == 0)
+                data->data_source_eof = TRUE;
+        else
+                g_byte_array_set_size (data->data_source_buffer, read);
+
+        g_debug ("[SEND_BODY] Resuming send");
+        nghttp2_session_resume_data (data->io->session, data->stream_id);
+}
+
+static void
+log_request_data (SoupHTTP2MessageData *data, const guint8 *buffer, gsize len)
+{
+        if (!data->logger)
+                return;
+
+        // NOTE: This doesn't exactly log data as it hits the network but
+        // rather as soon as we read it from our source which is as good
+        // as we can do since nghttp handles the actual io.
+        soup_logger_log_request_data (data->logger, data->msg, (const char *)buffer, len);
+}
+
+static ssize_t
+on_data_source_read_callback (nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, 
uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
+{
+        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, stream_id);
+        SoupMessageIOHTTP2 *io = get_io_data (data->msg);
+
+        if (data->paused) {
+                g_debug ("[SEND_BODY] Paused");
+                return NGHTTP2_ERR_PAUSE;
+        }
+
+        /* This cancellable is only used for async data source operations,
+         * only exists while reading is happening, and will be cancelled
+         * at any point if the data is freed.
+         */
+        if (!data->data_source_cancellable)
+                data->data_source_cancellable = g_cancellable_new ();
+
+        /* We support pollable streams in the best case because they
+         * should perform better with one fewer copy of each buffer and no threading. */
+        if (G_IS_POLLABLE_INPUT_STREAM (source->ptr) && g_pollable_input_stream_can_poll 
(G_POLLABLE_INPUT_STREAM (source->ptr))) {
+                GPollableInputStream *in_stream = G_POLLABLE_INPUT_STREAM (source->ptr);
+                GError *error = NULL;
+
+                gssize read = g_pollable_input_stream_read_nonblocking  (in_stream, buf, length, 
data->cancellable, &error);
+
+                if (read) {
+                        g_debug ("[SEND_BODY] Read %zu", read);
+                        log_request_data (data, buf, read);
+                }
+
+                if (read < 0) {
+                        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                                g_assert (data->data_source_poll == NULL);
+
+                                g_debug ("[SEND_BODY] Polling");
+                                data->data_source_poll = g_pollable_input_stream_create_source (in_stream, 
data->data_source_cancellable);
+                                g_source_set_callback (data->data_source_poll, 
(GSourceFunc)on_data_readable, data, NULL);
+                                g_source_set_priority (data->data_source_poll, get_data_io_priority (data));
+                                g_source_attach (data->data_source_poll, g_main_context_get_thread_default 
());
+
+                                g_error_free (error);
+                                return NGHTTP2_ERR_DEFERRED;
+                        }
+
+                        g_debug ("[SEND_BODY] Error %s", error->message);
+                        set_error_for_data (io, data, g_steal_pointer (&error));
+                        data->state = STATE_ERROR;
+                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+                }
+                else if (read == 0) {
+                        g_debug ("[SEND_BODY] EOF");
+                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;                
+                }
+
+                return read;
+        } else {
+                GInputStream *in_stream = G_INPUT_STREAM (source->ptr);
+        
+                /* To support non-pollable input streams we always deffer reads
+                * and read async into a local buffer. The next time around we will
+                * send that buffer or error.
+                */
+                if (!data->data_source_buffer)
+                        data->data_source_buffer = g_byte_array_new ();
+
+                gsize buffer_len = data->data_source_buffer->len;
+                if (buffer_len) {
+                        g_debug ("[SEND_BODY] Sending %zu", buffer_len);
+                        g_assert (buffer_len <= length); // QUESTION: Maybe not reliable
+                        memcpy (buf, data->data_source_buffer->data, buffer_len);
+                        log_request_data (data, buf, buffer_len);
+                        g_byte_array_set_size (data->data_source_buffer, 0);
+                        return buffer_len;
+                } else if (data->data_source_eof) {
+                        g_debug ("[SEND_BODY] EOF");
+                        g_clear_object (&data->data_source_cancellable);
+                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+                        return 0;
+                } else if (data->data_source_error) {
+                        g_debug ("[SEND_BODY] Error %s", data->data_source_error->message);
+                        g_clear_object (&data->data_source_cancellable);
+                        set_error_for_data (io, data, g_steal_pointer (&data->data_source_error));
+                        data->state = STATE_ERROR;
+                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+                } else {
+                        g_debug ("[SEND_BODY] Reading async");
+                        g_byte_array_set_size (data->data_source_buffer, length);
+                        g_input_stream_read_async (in_stream, data->data_source_buffer->data, length,
+                                                   get_data_io_priority (data),
+                                                   data->data_source_cancellable,
+                                                   (GAsyncReadyCallback)on_data_read, data);
+                        return NGHTTP2_ERR_DEFERRED;
+                }
+        }
+}
+
+/* HTTP2 IO functions */
+
+static gboolean
+data_compare (gconstpointer a, gconstpointer b)
+{
+        SoupHTTP2MessageData *data1 = (SoupHTTP2MessageData *)a, *data2 = (SoupHTTP2MessageData *)b;
+
+        return data1->msg == data2->msg;
+}
+
+static SoupHTTP2MessageData *
+add_message_to_io_data (SoupMessageIOHTTP2 *io,
+                        SoupMessageQueueItem *item,
+                        SoupMessageIOCompletionFn completion_cb,
+                        gpointer completion_data)
+{
+        SoupHTTP2MessageData *data = g_new0 (SoupHTTP2MessageData, 1);
+
+        data->item = soup_message_queue_item_ref (item);
+        data->msg = item->msg;
+        data->metrics = soup_message_get_metrics (data->msg);
+        data->cancellable = item->cancellable;
+        data->completion_cb = completion_cb;
+        data->completion_data = completion_data;
+        data->stream_id = 0; // Will be overwritten
+        data->io = io;
+
+        if (g_ptr_array_find_with_equal_func (io->messages, data, data_compare, NULL))
+                g_warn_if_reached ();
+        g_ptr_array_add (io->messages, data);
+
+        return data;
+}
+
+static void
+soup_http2_message_data_free (SoupHTTP2MessageData *data)
+{
+        g_clear_pointer (&data->item, soup_message_queue_item_unref);
+        g_clear_object (&data->memory_data_istream);
+        g_clear_object (&data->decoded_data_istream);
+
+        if (data->io_source) {
+                g_source_destroy (data->io_source);
+                g_clear_pointer (&data->io_source, g_source_unref);
+        }
+
+        if (data->data_source_poll)
+                g_source_destroy (data->data_source_poll);
+        g_clear_pointer (&data->data_source_poll, g_source_unref);
+
+        g_clear_error (&data->data_source_error);
+        g_clear_pointer (&data->data_source_buffer, g_byte_array_unref);
+
+        if (data->data_source_cancellable) {
+                g_cancellable_cancel (data->data_source_cancellable);
+                g_clear_object (&data->data_source_cancellable);
+        }
+
+        g_free (data);
+}
+
+#define MAKE_NV(NAME, VALUE, VALUELEN)                                      \
+        {                                                                   \
+                (uint8_t *)NAME, (uint8_t *)VALUE, strlen (NAME), VALUELEN, \
+                    NGHTTP2_NV_FLAG_NONE                                    \
+        }
+
+#define MAKE_NV2(NAME, VALUE)                                                     \
+        {                                                                         \
+                (uint8_t *)NAME, (uint8_t *)VALUE, strlen (NAME), strlen (VALUE), \
+                    NGHTTP2_NV_FLAG_NONE                                          \
+        }
+
+#define MAKE_NV3(NAME, VALUE, FLAGS)                                              \
+        {                                                                         \
+                (uint8_t *)NAME, (uint8_t *)VALUE, strlen (NAME), strlen (VALUE), \
+                    FLAGS                                                         \
+        }
+
+static void
+send_message_request (SoupMessage *msg, SoupMessageIOHTTP2 *io, SoupHTTP2MessageData *data)
+{
+        GArray *headers = g_array_new (FALSE, FALSE, sizeof (nghttp2_nv));
+
+        GUri *uri = soup_message_get_uri (msg);
+        char *host = soup_uri_get_host_for_headers (uri);
+        char *authority = g_strdup_printf ("%s:%u", host, g_uri_get_port (uri));
+
+        char *path_and_query;
+        if (soup_message_get_is_options_ping (msg))
+                path_and_query = g_strdup ("*");
+        else
+                path_and_query = g_strdup_printf ("%s%c%s", g_uri_get_path (uri), g_uri_get_query (uri) ? 
'?' : '\0', g_uri_get_query (uri));
+
+        const nghttp2_nv pseudo_headers[] = {
+                MAKE_NV3 (":method", soup_message_get_method (msg), NGHTTP2_NV_FLAG_NO_COPY_VALUE),
+                MAKE_NV2 (":scheme", g_uri_get_scheme (uri)),
+                MAKE_NV2 (":authority", authority),
+                MAKE_NV2 (":path", path_and_query),
+        };
+
+        for (guint i = 0; i < G_N_ELEMENTS (pseudo_headers); ++i) {
+                g_array_append_val (headers, pseudo_headers[i]);
+        }
+
+        SoupMessageHeadersIter iter;
+        const char *name, *value;
+        soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg));
+        while (soup_message_headers_iter_next (&iter, &name, &value)) {
+                /* Forbidden headers. TODO: Avoid setting this elsewhere? */
+                if (g_ascii_strcasecmp (name, "Transfer-Encoding") == 0)
+                        continue;
+                const nghttp2_nv nv = MAKE_NV2 (name, value);
+                g_array_append_val (headers, nv);
+        }
+        
+        GInputStream *body_stream = soup_message_get_request_body_stream (msg);
+        SoupSessionFeature *logger = soup_session_get_feature_for_message (data->item->session, 
SOUP_TYPE_LOGGER, data->msg);
+        if (logger && body_stream)
+                data->logger = SOUP_LOGGER (logger);
+
+        nghttp2_data_provider *data_provider = NULL;
+        if (body_stream) {
+                data_provider = g_new (nghttp2_data_provider, 1);
+                data_provider->source.ptr = body_stream;
+                data_provider->read_callback = on_data_source_read_callback;
+        }
+
+        data->stream_id = nghttp2_submit_request (io->session, NULL, (const nghttp2_nv *)headers->data, 
headers->len, data_provider, data);
+
+        h2_debug (io, data, "[SESSION] Request made for %s%s", authority, path_and_query);
+
+        g_array_free (headers, TRUE);
+        g_free (authority);
+        g_free (host);
+        g_free (path_and_query);
+        g_free (data_provider);
+}
+
+
+
+static void
+soup_message_io_http2_send_item (SoupClientMessageIO *iface,
+                                 SoupMessageQueueItem *item,
+                                 SoupMessageIOCompletionFn completion_cb,
+                                 gpointer user_data)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = add_message_to_io_data (io, item, completion_cb, user_data);
+
+        send_message_request (item->msg, io, data);
+}
+
+static SoupHTTP2MessageData *
+get_data_for_message (SoupMessageIOHTTP2 *io, SoupMessage *msg)
+{
+        const guint len = io->messages->len;
+
+        for (uint i = 0; i < len; ++i) {
+                SoupHTTP2MessageData *data = io->messages->pdata[i];
+                if (data->msg == msg)
+                        return data;
+        }
+
+        g_warn_if_reached ();
+        return NULL;
+}
+
+static void
+soup_message_io_http2_finished (SoupClientMessageIO *iface,
+                                SoupMessage         *msg)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data;
+       SoupMessageIOCompletionFn completion_cb;
+       gpointer completion_data;
+       SoupMessageIOCompletion completion;
+
+        data = get_data_for_message (io, msg);
+
+        h2_debug (io, data, "Finished");
+
+        // int ret;
+        // ret = nghttp2_submit_rst_stream (io->session, NGHTTP2_FLAG_NONE, data->stream_id, 
NGHTTP2_STREAM_CLOSED);
+        // g_assert (ret == 0);
+        // ret = nghttp2_session_terminate_session (io->session, NGHTTP2_NO_ERROR);
+        // g_assert (ret == 0);
+
+       completion_cb = data->completion_cb;
+       completion_data = data->completion_data;
+
+       // TODO
+       completion = SOUP_MESSAGE_IO_COMPLETE;
+
+       g_object_ref (msg);
+
+        nghttp2_session_set_stream_user_data (io->session, data->stream_id, NULL);
+        if (!g_ptr_array_remove_fast (io->messages, data))
+                g_warn_if_reached ();
+
+        soup_connection_message_io_finished (soup_message_get_connection (msg), msg);
+
+       if (completion_cb)
+               completion_cb (G_OBJECT (msg), completion, completion_data);
+
+       g_object_unref (msg);
+}
+
+static void
+soup_message_io_http2_pause (SoupClientMessageIO *iface,
+                             SoupMessage         *msg)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        g_debug ("soup_message_io_http2_pause");
+
+        if (data->paused)
+                g_warn_if_reached ();
+
+        data->paused = TRUE;
+}
+
+static void
+soup_message_io_http2_unpause (SoupClientMessageIO *iface,
+                               SoupMessage         *msg)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        g_debug ("soup_message_io_http2_unpause");
+
+        if (!data->paused)
+                g_warn_if_reached ();
+
+        data->paused = FALSE;
+}
+
+static void
+soup_message_io_http2_stolen (SoupClientMessageIO *iface)
+{
+        g_assert_not_reached ();
+}
+
+static gboolean
+soup_message_io_http2_in_progress (SoupClientMessageIO *iface,
+                                   SoupMessage         *msg)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        return data->state < STATE_WRITE_DONE;
+}
+
+static gboolean
+soup_message_io_http2_is_paused (SoupClientMessageIO *iface,
+                                 SoupMessage         *msg)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        return data->paused;
+}
+
+static gboolean
+soup_message_io_http2_is_reusable (SoupClientMessageIO *iface)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+
+        // TODO: This logic is probably incomplete
+        return !io->is_shutdown;
+}
+
+static gboolean
+message_source_check (GSource *source)
+{
+       SoupMessageIOSource *message_source = (SoupMessageIOSource *)source;
+        SoupMessage *msg = SOUP_MESSAGE (message_source->msg);
+        SoupMessageIOHTTP2 *io = get_io_data (msg);
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        //QUESTION: What is the point of message_source->paused
+
+        return !data->paused;
+}
+
+static GSource *
+soup_message_io_http2_get_source (SoupMessage *msg,
+                                  GCancellable *cancellable,
+                                  SoupMessageIOSourceFunc callback,
+                                  gpointer user_data)
+{
+        SoupMessageIOHTTP2 *io = get_io_data (msg);
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+        //GPollableInputStream *istream;
+
+        //g_debug ("soup_message_io_http2_get_source state=%u, paused=%d", data->state, data->paused);
+
+        //g_debug ("Queue %lu", nghttp2_session_get_outbound_queue_size (io->session));
+
+        GSource *base_source;
+        // TODO: Handle mixing writes in
+        if (data->paused)
+                base_source = cancellable ? g_cancellable_source_new (cancellable) : NULL;
+        else if (data->state < STATE_WRITE_DONE)
+                base_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM 
(io->ostream), cancellable);
+        else if (data->state < STATE_READ_DONE)
+                base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (io->istream), 
cancellable);
+        else
+                g_assert_not_reached ();
+
+        GSource *source = soup_message_io_source_new (base_source, G_OBJECT (msg), data->paused, 
message_source_check);
+        g_source_set_callback (source, (GSourceFunc)callback, user_data, NULL);
+        return source;
+}
+
+static void
+soup_message_io_http2_skip_body (SoupClientMessageIO *iface,
+                                 SoupMessage         *msg)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        g_debug ("soup_message_io_http2_skip_body");
+
+        g_assert (data->memory_data_istream);
+
+        soup_memory_input_stream_complete (SOUP_MEMORY_INPUT_STREAM (data->memory_data_istream));
+        data->state = STATE_READ_DONE;
+        soup_message_got_body (data->msg);
+}
+
+#if 0
+static int
+idle_finish (gpointer user_data)
+{
+        SoupMessage *msg = user_data;
+        soup_message_io_http2_finished (msg); // TODO: Smarter
+        return G_SOURCE_REMOVE;
+}
+#endif
+
+static void
+client_stream_eof (SoupClientInputStream *stream, gpointer user_data)
+{
+       SoupMessage *msg = user_data;
+       SoupMessageIOHTTP2 *io = get_io_data (msg);
+
+        if (!io) {
+                g_warn_if_reached (); // QUESTION: Probably fine
+                return;
+        }
+
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+
+        g_debug ("client_stream_eof %d", SOUP_STATUS_IS_REDIRECTION (soup_message_get_status (data->msg)));
+
+        data->state = STATE_READ_DONE;
+        // data->item->state = SOUP_MESSAGE_FINISHED;
+        // soup_message_io_http2_finished (msg);
+        // g_idle_add (idle_finish, msg); // TODO
+}
+
+static GInputStream *
+soup_message_io_http2_get_response_istream (SoupClientMessageIO *iface,
+                                            SoupMessage         *msg,
+                                            GError **error)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+        GInputStream *client_stream, *base_stream;
+
+        g_debug ("soup_message_io_http2_get_response_istream paused=%d", data->paused);
+
+        if (data->decoded_data_istream)
+                base_stream = g_object_ref (data->decoded_data_istream);
+        else /* For example with status_code == SOUP_STATUS_NO_CONTENT */
+                base_stream = g_memory_input_stream_new ();
+
+        client_stream = soup_client_input_stream_new (base_stream, msg);
+        g_signal_connect (client_stream, "eof", G_CALLBACK (client_stream_eof), msg);
+
+        g_object_unref (base_stream);
+
+        return client_stream;
+}
+
+static gboolean
+io_read (SoupMessageIOHTTP2 *io,
+         gboolean blocking,
+        GCancellable *cancellable,
+         GError **error)
+{
+        guint8 buffer[8192];
+        gssize read;
+        int ret;
+
+        if ((read = g_pollable_stream_read (io->istream, buffer, sizeof (buffer),
+                                            blocking, cancellable, error)) < 0)
+            return FALSE;
+
+        ret = nghttp2_session_mem_recv (io->session, buffer, read);
+        return ret != 0;
+}
+
+static gboolean
+io_write (SoupMessageIOHTTP2 *io,
+         gboolean blocking,
+        GCancellable *cancellable,
+         GError **error)
+{
+        /* We must write all of nghttp2's buffer before we ask for more */
+
+        if (io->written_bytes == io->write_buffer_size)
+                io->write_buffer = NULL;
+
+        if (io->write_buffer == NULL) {
+                io->written_bytes = 0;
+                io->write_buffer_size = nghttp2_session_mem_send (io->session, (const 
guint8**)&io->write_buffer);
+                if (io->write_buffer_size == 0) {
+                        /* Done */
+                        io->write_buffer = NULL;
+                        return TRUE;
+                }
+        }
+
+        gssize ret = g_pollable_stream_write (io->ostream,
+                                              io->write_buffer + io->written_bytes,
+                                              io->write_buffer_size - io->written_bytes,
+                                              blocking, cancellable, error);
+        if (ret < 0)
+                return FALSE;
+
+        io->written_bytes += ret;
+        return TRUE;
+}
+
+static gboolean
+io_read_or_write (SoupMessageIOHTTP2 *io,
+                  gboolean blocking,
+                  GCancellable *cancellable,
+                  GError **error)
+{
+        // TODO: This can possibly more inteligent about what actually needs
+        // writing so we can prioritize better.
+        if (nghttp2_session_want_write (io->session))
+                return io_write (io, blocking, cancellable, error);
+        return io_read (io, blocking, cancellable, error);
+}
+
+static gboolean
+io_run_until (SoupMessage *msg,
+              gboolean blocking,
+             SoupHTTP2IOState state,
+             GCancellable *cancellable,
+              GError **error)
+{
+       SoupMessageIOHTTP2 *io = get_io_data (msg);
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+       gboolean progress = TRUE, done;
+       GError *my_error = NULL;
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return FALSE;
+       else if (!io) {
+               g_set_error_literal (error, G_IO_ERROR,
+                                    G_IO_ERROR_CANCELLED,
+                                    _("Operation was cancelled"));
+               return FALSE;
+       }
+
+       g_object_ref (msg);
+
+       while (progress && get_io_data (msg) == io && !data->paused && data->state < state) {
+                progress = io_read_or_write (io, blocking, cancellable, &my_error);
+       }
+
+       if (my_error || (my_error = get_error_for_data (io, data))) {
+               g_propagate_error (error, my_error);
+               g_object_unref (msg);
+               return FALSE;
+        } else if (get_io_data (msg) != io) {
+               g_set_error_literal (error, G_IO_ERROR,
+                                    G_IO_ERROR_CANCELLED,
+                                    _("Operation was cancelled"));
+               g_object_unref (msg);
+               return FALSE;
+       }       
+
+       done = data->state >= state;
+
+       if (!blocking && !done) {
+               g_set_error_literal (error, G_IO_ERROR,
+                                    G_IO_ERROR_WOULD_BLOCK,
+                                    _("Operation would block"));
+               g_object_unref (msg);
+               return FALSE;
+       }
+
+       g_object_unref (msg);
+       return done;
+}
+
+
+static gboolean
+soup_message_io_http2_run_until_read (SoupClientMessageIO *iface,
+                                      SoupMessage         *msg,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+        //SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        return io_run_until (msg, TRUE, STATE_READ_DATA, cancellable, error);
+}
+
+static gboolean
+soup_message_io_http2_run_until_finish (SoupClientMessageIO *iface,
+                                        SoupMessage         *msg,
+                                        gboolean             blocking,
+                                        GCancellable        *cancellable,
+                                        GError             **error)
+{
+
+        g_debug ("soup_message_io_http2_run_until_finish");
+
+        //QUESTION: Prematurely end the stream, we don't need more than what we are getting
+        //nghttp2_submit_rst_stream (io->session, NGHTTP2_FLAG_NONE, data->stream_id, NGHTTP2_STREAM_CLOSED);
+
+        return io_run_until (msg, blocking, STATE_READ_DONE, cancellable, error);
+}
+
+static void
+soup_message_io_http2_run (SoupClientMessageIO *iface,
+                           SoupMessage         *msg,
+                          gboolean             blocking)
+{
+        g_assert_not_reached ();
+}
+
+static void io_run_until_read_async (SoupMessage *msg,
+                                     GTask       *task);
+
+static gboolean
+io_run_until_read_ready (SoupMessage *msg,
+                         gpointer     user_data)
+{
+        GTask *task = user_data;
+
+        io_run_until_read_async (msg, task);
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+io_run_until_read_async (SoupMessage *msg,
+                         GTask       *task)
+{
+        SoupMessageIOHTTP2 *io = get_io_data (msg);
+        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
+        GError *error = NULL;
+
+        if (data->io_source) {
+                g_source_destroy (data->io_source);
+                g_clear_pointer (&data->io_source, g_source_unref);
+        }
+
+        if (io_run_until (msg, FALSE,
+                          STATE_READ_DATA,
+                          g_task_get_cancellable (task),
+                          &error)) {
+                g_task_return_boolean (task, TRUE);
+                g_object_unref (task);
+                return;
+        }
+
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                g_error_free (error);
+                data->io_source = soup_message_io_http2_get_source (msg, g_task_get_cancellable (task),
+                                                                  
(SoupMessageIOSourceFunc)io_run_until_read_ready,
+                                                                  task);
+               g_source_set_priority (data->io_source, g_task_get_priority (task));
+                g_source_attach (data->io_source, io->async_context);
+                return;
+        }
+
+        if (get_io_data (msg) == io)
+                soup_message_io_http2_finished ((SoupClientMessageIO *)io, msg);
+        else
+                g_warn_if_reached ();
+
+        g_task_return_error (task, error);
+        g_object_unref (task);
+}
+
+static void
+soup_message_io_http2_run_until_read_async (SoupClientMessageIO *iface,
+                                            SoupMessage         *msg,
+                                            int                  io_priority,
+                                            GCancellable        *cancellable,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             user_data)
+{
+        GTask *task;
+
+        task = g_task_new (msg, cancellable, callback, user_data);
+       g_task_set_priority (task, io_priority);
+        io_run_until_read_async (msg, task);
+}
+
+static gboolean
+soup_message_io_http2_is_open (SoupClientMessageIO *iface)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+        gboolean ret = TRUE;
+
+        GError *error = NULL;
+        if (!io_read (io, FALSE, NULL, &error)) {
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+                        ret = FALSE;
+
+                g_clear_error (&error);
+        }
+
+        h2_debug (io, NULL, "[SESSION] Open=%d", ret);
+
+        return ret;
+}
+
+static void
+soup_message_io_http2_destroy (SoupClientMessageIO *iface)
+{
+        SoupMessageIOHTTP2 *io = (SoupMessageIOHTTP2 *)iface;
+
+        g_debug ("soup_message_io_http2_destroy");
+
+        g_clear_object (&io->stream);
+        g_clear_pointer (&io->async_context, g_main_context_unref);
+        g_clear_pointer (&io->session, nghttp2_session_del);
+        g_clear_pointer (&io->messages, g_ptr_array_unref);
+        g_clear_pointer (&io->message_errors, g_hash_table_unref);
+
+        g_free (io);
+}
+
+static const SoupClientMessageIOFuncs io_funcs = {
+        soup_message_io_http2_destroy,
+        soup_message_io_http2_finished,
+        soup_message_io_http2_stolen,
+        soup_message_io_http2_send_item,
+        soup_message_io_http2_get_response_istream,
+        soup_message_io_http2_pause,
+        soup_message_io_http2_unpause,
+        soup_message_io_http2_is_paused,
+        soup_message_io_http2_run,
+        soup_message_io_http2_run_until_read,
+        soup_message_io_http2_run_until_read_async,
+        soup_message_io_http2_run_until_finish,
+        soup_message_io_http2_in_progress,
+        soup_message_io_http2_skip_body,
+        soup_message_io_http2_is_open,
+        soup_message_io_http2_is_reusable
+        // soup_message_io_http2_get_source
+};
+
+static void
+soup_client_message_io_http2_init (SoupMessageIOHTTP2 *io)
+{
+        // FIXME: Abort on out of memory errors
+        nghttp2_session_callbacks *callbacks;
+        nghttp2_session_callbacks_new (&callbacks);
+        nghttp2_session_callbacks_set_on_header_callback (callbacks, on_header_callback);
+        nghttp2_session_callbacks_set_on_frame_recv_callback (callbacks, on_frame_recv_callback);
+        nghttp2_session_callbacks_set_on_data_chunk_recv_callback (callbacks, on_data_chunk_recv_callback);
+        nghttp2_session_callbacks_set_on_begin_frame_callback (callbacks, on_begin_frame_callback);
+        nghttp2_session_callbacks_set_before_frame_send_callback (callbacks, on_before_frame_send_callback);
+        nghttp2_session_callbacks_set_on_frame_not_send_callback (callbacks, on_frame_not_send_callback);
+        nghttp2_session_callbacks_set_on_frame_send_callback (callbacks, on_frame_send_callback);
+        nghttp2_session_callbacks_set_on_stream_close_callback (callbacks, on_stream_close_callback);
+
+        nghttp2_session_client_new (&io->session, callbacks, io);
+        nghttp2_session_callbacks_del (callbacks);
+
+        io->messages = g_ptr_array_new_full (1, (GDestroyNotify)soup_http2_message_data_free);
+        /* Errors are stored separate as they have a longer lifetime than MessageData */
+        io->message_errors = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
(GDestroyNotify)g_error_free);
+
+        io->iface.funcs = &io_funcs;
+}
+
+#define INITIAL_WINDOW_SIZE (32 * 1024 * 1024) // 32MB matches other implementations
+#define MAX_HEADER_TABLE_SIZE 65536 // Match size used by Chromium/Firefox
+
+SoupClientMessageIO *
+soup_client_message_io_http2_new (GIOStream *stream)
+{
+        SoupMessageIOHTTP2 *io = g_new0 (SoupMessageIOHTTP2, 1);
+        soup_client_message_io_http2_init (io);
+
+        io->stream = g_object_ref (stream);
+        io->istream = g_io_stream_get_input_stream (io->stream);
+        io->ostream = g_io_stream_get_output_stream (io->stream);
+
+        io->async_context = g_main_context_ref_thread_default ();
+
+        nghttp2_session_set_local_window_size (io->session, NGHTTP2_FLAG_NONE, 0, INITIAL_WINDOW_SIZE);
+
+        const nghttp2_settings_entry settings[] = {
+                { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, INITIAL_WINDOW_SIZE },
+                { NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE },
+                { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 },
+        };
+        nghttp2_submit_settings (io->session, NGHTTP2_FLAG_NONE, settings, G_N_ELEMENTS (settings));
+
+        return (SoupClientMessageIO *)io;
+}
\ No newline at end of file
diff --git a/libsoup/http2/soup-client-message-io-http2.h b/libsoup/http2/soup-client-message-io-http2.h
new file mode 100644
index 00000000..e96934ac
--- /dev/null
+++ b/libsoup/http2/soup-client-message-io-http2.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2021 Igalia S.L.
+ */
+
+#pragma once
+
+#include "soup-client-message-io.h"
+
+G_BEGIN_DECLS
+
+SoupClientMessageIO *           soup_client_message_io_http2_new       (GIOStream                 *stream);
+
+G_END_DECLS
diff --git a/libsoup/http2/soup-memory-input-stream.c b/libsoup/http2/soup-memory-input-stream.c
new file mode 100644
index 00000000..88add784
--- /dev/null
+++ b/libsoup/http2/soup-memory-input-stream.c
@@ -0,0 +1,611 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ * Copyright 2021 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Christian Kellner <gicmo gnome org> 
+ */
+
+#include "config.h"
+
+#include "soup-memory-input-stream.h"
+#include <glib/gi18n-lib.h>
+
+/**
+ * SECTION:SoupMemoryInputStream
+ * @short_description: Streaming input operations on memory chunks
+ * @include: gio/gio.h
+ * @see_also: #SoupMemoryOutputStream
+ *
+ * #SoupMemoryInputStream is a class for using arbitrary
+ * memory chunks as input for GIO streaming input operations.
+ *
+ * It differs from #GMemoryInputStream in that it frees older chunks
+ * after they have been read, returns #G_IO_ERROR_WOULDBLOCK at the end
+ * of data until soup_memory_input_stream_complete() is called, and implements
+ * g_pollable_input_stream_is_readable().
+ */
+
+struct _SoupMemoryInputStream {
+        GInputStream parent_instance;
+};
+
+typedef struct {
+        GSList *chunks;
+        GPollableInputStream *parent_stream;
+        gsize start_offset;
+        gsize len;
+        gsize pos;
+        gboolean completed;
+} SoupMemoryInputStreamPrivate;
+
+static gssize soup_memory_input_stream_read (GInputStream *stream,
+                                             void *buffer,
+                                             gsize count,
+                                             GCancellable *cancellable,
+                                             GError **error);
+static gssize soup_memory_input_stream_skip (GInputStream *stream,
+                                             gsize count,
+                                             GCancellable *cancellable,
+                                             GError **error);
+static gboolean soup_memory_input_stream_close (GInputStream *stream,
+                                                GCancellable *cancellable,
+                                                GError **error);
+static void soup_memory_input_stream_skip_async (GInputStream *stream,
+                                                 gsize count,
+                                                 int io_priority,
+                                                 GCancellable *cancellabl,
+                                                 GAsyncReadyCallback callback,
+                                                 gpointer datae);
+static gssize soup_memory_input_stream_skip_finish (GInputStream *stream,
+                                                    GAsyncResult *result,
+                                                    GError **error);
+static void soup_memory_input_stream_close_async (GInputStream *stream,
+                                                  int io_priority,
+                                                  GCancellable *cancellabl,
+                                                  GAsyncReadyCallback callback,
+                                                  gpointer data);
+static gboolean soup_memory_input_stream_close_finish (GInputStream *stream,
+                                                       GAsyncResult *result,
+                                                       GError **error);
+
+static void soup_memory_input_stream_seekable_iface_init (GSeekableIface *iface);
+static goffset soup_memory_input_stream_tell (GSeekable *seekable);
+static gboolean soup_memory_input_stream_can_seek (GSeekable *seekable);
+static gboolean soup_memory_input_stream_seek (GSeekable *seekable,
+                                               goffset offset,
+                                               GSeekType type,
+                                               GCancellable *cancellable,
+                                               GError **error);
+static gboolean soup_memory_input_stream_can_truncate (GSeekable *seekable);
+static gboolean soup_memory_input_stream_truncate (GSeekable *seekable,
+                                                   goffset offset,
+                                                   GCancellable *cancellable,
+                                                   GError **error);
+
+static void soup_memory_input_stream_pollable_iface_init (GPollableInputStreamInterface *iface);
+static gboolean soup_memory_input_stream_is_readable (GPollableInputStream *stream);
+static GSource *soup_memory_input_stream_create_source (GPollableInputStream *stream,
+                                                        GCancellable *cancellable);
+static gssize soup_memory_input_stream_read_nonblocking (GPollableInputStream *stream,
+                                                         void *buffer,
+                                                         gsize count,
+                                                         GError **error);
+
+static void soup_memory_input_stream_dispose (GObject *object);
+static void soup_memory_input_stream_finalize (GObject *object);
+
+G_DEFINE_TYPE_WITH_CODE (SoupMemoryInputStream, soup_memory_input_stream, G_TYPE_INPUT_STREAM,
+                         G_ADD_PRIVATE (SoupMemoryInputStream)
+                             G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE,
+                                                    soup_memory_input_stream_seekable_iface_init);
+                         G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
+                                                soup_memory_input_stream_pollable_iface_init);)
+
+enum {
+        WANT_READ,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void
+soup_memory_input_stream_class_init (SoupMemoryInputStreamClass *klass)
+{
+        GObjectClass *object_class;
+        GInputStreamClass *istream_class;
+
+        object_class = G_OBJECT_CLASS (klass);
+        object_class->finalize = soup_memory_input_stream_finalize;
+        object_class->dispose = soup_memory_input_stream_dispose;
+
+        istream_class = G_INPUT_STREAM_CLASS (klass);
+        istream_class->read_fn = soup_memory_input_stream_read;
+        istream_class->skip = soup_memory_input_stream_skip;
+        istream_class->close_fn = soup_memory_input_stream_close;
+
+        istream_class->skip_async = soup_memory_input_stream_skip_async;
+        istream_class->skip_finish = soup_memory_input_stream_skip_finish;
+        istream_class->close_async = soup_memory_input_stream_close_async;
+        istream_class->close_finish = soup_memory_input_stream_close_finish;
+
+        signals[WANT_READ] =
+                g_signal_new ("want-read",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              0,
+                              NULL, NULL,
+                              NULL,
+                              G_TYPE_ERROR,
+                              2, G_TYPE_CANCELLABLE, G_TYPE_BOOLEAN);
+}
+
+static void
+soup_memory_input_stream_dispose (GObject *object)
+{
+        SoupMemoryInputStream *stream = SOUP_MEMORY_INPUT_STREAM (object);
+        SoupMemoryInputStreamPrivate *priv = soup_memory_input_stream_get_instance_private (stream);
+
+        priv->completed = TRUE;
+
+        G_OBJECT_CLASS (soup_memory_input_stream_parent_class)->dispose (object);
+}
+
+static void
+soup_memory_input_stream_finalize (GObject *object)
+{
+        SoupMemoryInputStream *stream = SOUP_MEMORY_INPUT_STREAM (object);
+        SoupMemoryInputStreamPrivate *priv = soup_memory_input_stream_get_instance_private (stream);
+
+        g_slist_free_full (priv->chunks, (GDestroyNotify)g_bytes_unref);
+        g_clear_object (&priv->parent_stream);
+
+        G_OBJECT_CLASS (soup_memory_input_stream_parent_class)->finalize (object);
+}
+
+static void
+soup_memory_input_stream_seekable_iface_init (GSeekableIface *iface)
+{
+        iface->tell = soup_memory_input_stream_tell;
+        iface->can_seek = soup_memory_input_stream_can_seek;
+        iface->seek = soup_memory_input_stream_seek;
+        iface->can_truncate = soup_memory_input_stream_can_truncate;
+        iface->truncate_fn = soup_memory_input_stream_truncate;
+}
+
+static void
+soup_memory_input_stream_pollable_iface_init (GPollableInputStreamInterface *iface)
+{
+        iface->is_readable = soup_memory_input_stream_is_readable;
+        iface->create_source = soup_memory_input_stream_create_source;
+        iface->read_nonblocking = soup_memory_input_stream_read_nonblocking;
+}
+
+static void
+soup_memory_input_stream_init (SoupMemoryInputStream *stream)
+{
+}
+
+/**
+ * soup_memory_input_stream_new:
+ *
+ * Creates a new empty #SoupMemoryInputStream. 
+ *
+ * Returns: a new #GInputStream
+ */
+GInputStream *
+soup_memory_input_stream_new (GPollableInputStream *parent_stream)
+{
+        GInputStream *stream;
+        SoupMemoryInputStreamPrivate *priv;
+
+        stream = g_object_new (SOUP_TYPE_MEMORY_INPUT_STREAM, NULL);
+        priv = soup_memory_input_stream_get_instance_private (SOUP_MEMORY_INPUT_STREAM (stream));
+        if (parent_stream)
+                priv->parent_stream = g_object_ref (parent_stream);
+
+        return stream;
+}
+
+void
+soup_memory_input_stream_add_bytes (SoupMemoryInputStream *stream,
+                                    GBytes *bytes)
+{
+        SoupMemoryInputStreamPrivate *priv;
+
+        g_return_if_fail (SOUP_IS_MEMORY_INPUT_STREAM (stream));
+        g_return_if_fail (bytes != NULL);
+
+        priv = soup_memory_input_stream_get_instance_private (stream);
+
+        priv->chunks = g_slist_append (priv->chunks, g_bytes_ref (bytes));
+        priv->len += g_bytes_get_size (bytes);
+}
+
+static gssize
+soup_memory_input_stream_read_real (GInputStream *stream,
+                                    gboolean blocking,
+                                    void *buffer,
+                                    gsize read_count,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+        SoupMemoryInputStream *memory_stream;
+        SoupMemoryInputStreamPrivate *priv;
+        GSList *l;
+        GBytes *chunk;
+        gsize len;
+        gsize offset, start, rest, size;
+        gsize count;
+
+        memory_stream = SOUP_MEMORY_INPUT_STREAM (stream);
+        priv = soup_memory_input_stream_get_instance_private (memory_stream);
+
+        /* We have a list of chunked bytes that we continually read from.
+         * Once a chunk is fully read it is removed from our list and we
+         * keep the offset of where the chunks start.
+         */
+
+        count = MIN (read_count, priv->len - priv->pos);
+
+        offset = priv->start_offset;
+        for (l = priv->chunks; l; l = l->next) {
+                chunk = (GBytes *)l->data;
+                len = g_bytes_get_size (chunk);
+
+                if (offset + len > priv->pos)
+                        break;
+
+                offset += len;
+        }
+
+        priv->start_offset = offset;
+        start = priv->pos - offset;
+        rest = count;
+
+        while (l && rest > 0) {
+                GSList *next = l->next;
+
+                const guint8 *chunk_data;
+                chunk = (GBytes *)l->data;
+
+                chunk_data = g_bytes_get_data (chunk, &len);
+
+                size = MIN (rest, len - start);
+
+                memcpy ((guint8 *)buffer + (count - rest), chunk_data + start, size);
+                rest -= size;
+
+                // Remove fully read chunk from list, note that we are always near the start of the list
+                if (start + size == len) {
+                        priv->start_offset += len;
+                        priv->chunks = g_slist_delete_link (priv->chunks, l);
+                        g_bytes_unref (chunk);
+                }
+
+                start = 0;
+                l = next;
+        }
+
+        priv->pos += count;
+
+        // We need to block until the read is completed.
+        // So emit a signal saying we need more data.
+        if (count == 0 && blocking && !priv->completed) {
+                GError *read_error = NULL;
+                g_signal_emit (memory_stream, signals[WANT_READ], 0,
+                               cancellable,
+                               TRUE,
+                               &read_error);
+
+                if (read_error) {
+                        g_propagate_error (error, read_error);
+                        return -1;
+                }
+
+                return soup_memory_input_stream_read_real (
+                        stream, blocking, buffer, read_count, cancellable, error
+                );
+        }
+
+        return count;
+}
+
+static gssize
+soup_memory_input_stream_read (GInputStream *stream,
+                               void *buffer,
+                               gsize count,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+        return soup_memory_input_stream_read_real (stream, TRUE, buffer, count, cancellable, error);
+}
+
+static gssize
+soup_memory_input_stream_read_nonblocking (GPollableInputStream *stream,
+                                           void *buffer,
+                                           gsize count,
+                                           GError **error)
+{
+        SoupMemoryInputStream *memory_stream = SOUP_MEMORY_INPUT_STREAM (stream);
+        SoupMemoryInputStreamPrivate *priv = soup_memory_input_stream_get_instance_private (memory_stream);
+        GError *inner_error = NULL;
+
+        gsize read = soup_memory_input_stream_read_real (G_INPUT_STREAM (stream), FALSE, buffer, count, 
NULL, &inner_error);
+
+        if (read == 0 && !priv->completed && !inner_error) {
+                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
+
+                // Try requesting more reads from the io backend
+                GError *inner_error = NULL;
+                g_signal_emit (memory_stream, signals[WANT_READ], 0,
+                               NULL, FALSE, &inner_error);
+
+                // TODO: Do we care?
+                g_clear_error (&inner_error);
+
+                return -1;
+        }
+
+        if (inner_error)
+                g_propagate_error (error, inner_error);
+
+        return read;
+}
+
+void
+soup_memory_input_stream_complete (SoupMemoryInputStream *stream)
+{
+        SoupMemoryInputStreamPrivate *priv = soup_memory_input_stream_get_instance_private (stream);
+        priv->completed = TRUE;
+}
+
+static gssize
+soup_memory_input_stream_skip (GInputStream *stream,
+                               gsize count,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+        SoupMemoryInputStream *memory_stream;
+        SoupMemoryInputStreamPrivate *priv;
+
+        memory_stream = SOUP_MEMORY_INPUT_STREAM (stream);
+        priv = soup_memory_input_stream_get_instance_private (memory_stream);
+
+        count = MIN (count, priv->len - priv->pos);
+        priv->pos += count;
+
+        return count;
+}
+
+static gboolean
+soup_memory_input_stream_close (GInputStream *stream,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+        return TRUE;
+}
+
+static void
+soup_memory_input_stream_skip_async (GInputStream *stream,
+                                     gsize count,
+                                     int io_priority,
+                                     GCancellable *cancellable,
+                                     GAsyncReadyCallback callback,
+                                     gpointer user_data)
+{
+        GTask *task;
+        gssize nskipped;
+        GError *error = NULL;
+
+        nskipped = G_INPUT_STREAM_GET_CLASS (stream)->skip (stream, count, cancellable, &error);
+        task = g_task_new (stream, cancellable, callback, user_data);
+        g_task_set_source_tag (task, soup_memory_input_stream_skip_async);
+
+        if (error)
+                g_task_return_error (task, error);
+        else
+                g_task_return_int (task, nskipped);
+        g_object_unref (task);
+}
+
+static gssize
+soup_memory_input_stream_skip_finish (GInputStream *stream,
+                                      GAsyncResult *result,
+                                      GError **error)
+{
+        g_return_val_if_fail (g_task_is_valid (result, stream), -1);
+
+        return g_task_propagate_int (G_TASK (result), error);
+}
+
+static void
+soup_memory_input_stream_close_async (GInputStream *stream,
+                                      int io_priority,
+                                      GCancellable *cancellable,
+                                      GAsyncReadyCallback callback,
+                                      gpointer user_data)
+{
+        GTask *task;
+
+        task = g_task_new (stream, cancellable, callback, user_data);
+        g_task_set_source_tag (task, soup_memory_input_stream_close_async);
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+}
+
+static gboolean
+soup_memory_input_stream_close_finish (GInputStream *stream,
+                                       GAsyncResult *result,
+                                       GError **error)
+{
+        return TRUE;
+}
+
+static goffset
+soup_memory_input_stream_tell (GSeekable *seekable)
+{
+        SoupMemoryInputStream *memory_stream;
+        SoupMemoryInputStreamPrivate *priv;
+
+        memory_stream = SOUP_MEMORY_INPUT_STREAM (seekable);
+        priv = soup_memory_input_stream_get_instance_private (memory_stream);
+
+        return priv->pos;
+}
+
+static gboolean soup_memory_input_stream_can_seek (GSeekable *seekable)
+{
+        return FALSE;
+}
+
+static gboolean
+soup_memory_input_stream_seek (GSeekable *seekable,
+                               goffset offset,
+                               GSeekType type,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+        g_set_error_literal (error,
+                             G_IO_ERROR,
+                             G_IO_ERROR_NOT_SUPPORTED,
+                             _ ("Cannot seek SoupMemoryInputStream"));
+        return FALSE;
+}
+
+static gboolean
+soup_memory_input_stream_can_truncate (GSeekable *seekable)
+{
+        return FALSE;
+}
+
+static gboolean
+soup_memory_input_stream_truncate (GSeekable *seekable,
+                                   goffset offset,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+        g_set_error_literal (error,
+                             G_IO_ERROR,
+                             G_IO_ERROR_NOT_SUPPORTED,
+                             _ ("Cannot truncate SoupMemoryInputStream"));
+        return FALSE;
+}
+
+static gboolean
+soup_memory_input_stream_is_readable (GPollableInputStream *stream)
+{
+        SoupMemoryInputStream *memory_stream = SOUP_MEMORY_INPUT_STREAM (stream);
+        SoupMemoryInputStreamPrivate *priv = soup_memory_input_stream_get_instance_private (memory_stream);
+
+        return priv->pos < priv->len || priv->completed;
+}
+
+/* Custom GSource */
+
+typedef struct {
+       GSource source;
+       SoupMemoryInputStream *stream;
+} SoupMemoryStreamSource;
+
+static gboolean
+memory_stream_source_prepare (GSource *source,
+                              gint    *timeout)
+{
+        SoupMemoryStreamSource *stream_source = (SoupMemoryStreamSource *)source;
+        return soup_memory_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (stream_source->stream));
+}
+
+static gboolean
+memory_stream_source_dispatch (GSource     *source,
+                               GSourceFunc  callback,
+                               gpointer     user_data)
+{
+       GPollableSourceFunc func = (GPollableSourceFunc )callback;
+       SoupMemoryStreamSource *memory_source = (SoupMemoryStreamSource *)source;
+
+        if (!func)
+                return FALSE;
+
+       return (*func) (G_OBJECT (memory_source->stream), user_data);
+}
+
+static gboolean
+memory_stream_source_closure_callback (GObject *pollable_stream,
+                                      gpointer data)
+{
+       GClosure *closure = data;
+       GValue param = G_VALUE_INIT;
+       GValue result_value = G_VALUE_INIT;
+       gboolean result;
+
+       g_value_init (&result_value, G_TYPE_BOOLEAN);
+
+        g_assert (G_IS_POLLABLE_INPUT_STREAM (pollable_stream));
+       g_value_init (&param, G_TYPE_POLLABLE_INPUT_STREAM);
+       g_value_set_object (&param, pollable_stream);
+
+       g_closure_invoke (closure, &result_value, 1, &param, NULL);
+
+       result = g_value_get_boolean (&result_value);
+       g_value_unset (&result_value);
+       g_value_unset (&param);
+
+       return result;
+}
+
+static void
+memory_stream_source_finalize (GSource *source)
+{
+       SoupMemoryStreamSource *memory_source = (SoupMemoryStreamSource *)source;
+
+       g_object_unref (memory_source->stream);
+}
+
+static GSourceFuncs source_funcs =
+{
+       memory_stream_source_prepare,
+       NULL,
+       memory_stream_source_dispatch,
+       memory_stream_source_finalize,
+       (GSourceFunc)memory_stream_source_closure_callback,
+       NULL,
+};
+
+static GSource *
+soup_memory_input_stream_create_source (GPollableInputStream *stream,
+                                        GCancellable *cancellable)
+{
+        SoupMemoryInputStreamPrivate *priv = soup_memory_input_stream_get_instance_private 
(SOUP_MEMORY_INPUT_STREAM (stream));
+
+        GSource *source = g_source_new (&source_funcs, sizeof (SoupMemoryStreamSource));
+       g_source_set_name (source, "SoupMemoryStreamSource");
+
+       SoupMemoryStreamSource *stream_source = (SoupMemoryStreamSource *)source;
+        stream_source->stream = g_object_ref (SOUP_MEMORY_INPUT_STREAM (stream));
+
+        GSource *child_source;
+        if (priv->parent_stream)
+                child_source = g_pollable_input_stream_create_source (priv->parent_stream, cancellable);
+        else
+                child_source = g_cancellable_source_new (cancellable);
+
+        g_source_set_dummy_callback (child_source);
+        g_source_add_child_source (source, child_source);
+        g_source_unref (child_source);
+
+        return source;
+}
diff --git a/libsoup/http2/soup-memory-input-stream.h b/libsoup/http2/soup-memory-input-stream.h
new file mode 100644
index 00000000..3a26c1db
--- /dev/null
+++ b/libsoup/http2/soup-memory-input-stream.h
@@ -0,0 +1,16 @@
+
+#pragma once
+
+#include "soup-types.h"
+
+#define SOUP_TYPE_MEMORY_INPUT_STREAM (soup_memory_input_stream_get_type ())
+G_DECLARE_FINAL_TYPE (SoupMemoryInputStream, soup_memory_input_stream, SOUP, MEMORY_INPUT_STREAM, 
GInputStream)
+
+GInputStream * soup_memory_input_stream_new        (GPollableInputStream *parent_stream);
+
+void           soup_memory_input_stream_add_bytes (SoupMemoryInputStream *stream,
+                                                   GBytes                *bytes);
+
+void          soup_memory_input_stream_complete    (SoupMemoryInputStream *stream);
+
+G_END_DECLS
diff --git a/libsoup/meson.build b/libsoup/meson.build
index 1448f820..68de84cc 100644
--- a/libsoup/meson.build
+++ b/libsoup/meson.build
@@ -32,6 +32,9 @@ soup_sources = [
   'hsts/soup-hsts-enforcer-db.c',
   'hsts/soup-hsts-policy.c',
 
+  'http2/soup-client-message-io-http2.c',
+  'http2/soup-memory-input-stream.c',
+
   'server/soup-auth-domain.c',
   'server/soup-auth-domain-basic.c',
   'server/soup-auth-domain-digest.c',
@@ -180,6 +183,7 @@ deps = [
   brotlidec_dep,
   platform_deps,
   libz_dep,
+  libnghttp2_dep,
 ]
 
 libsoup_includes = [
@@ -189,6 +193,7 @@ libsoup_includes = [
     'cookies',
     'content-decoder',
     'hsts',
+    'http2',
     'server',
     'websocket',
     '.'
diff --git a/libsoup/soup-client-message-io-http1.c b/libsoup/soup-client-message-io-http1.c
index a43ca715..2443a51d 100644
--- a/libsoup/soup-client-message-io-http1.c
+++ b/libsoup/soup-client-message-io-http1.c
@@ -68,13 +68,13 @@ soup_client_message_io_http1_get_priority (SoupClientMessageIOHTTP1 *io)
 }
 
 static void
-soup_client_message_io_http1_finished (SoupClientMessageIO *iface)
+soup_client_message_io_http1_finished (SoupClientMessageIO *iface,
+                                       SoupMessage         *msg)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
         SoupMessageIOCompletionFn completion_cb;
         gpointer completion_data;
         SoupMessageIOCompletion completion;
-        SoupMessage *msg;
 
         completion_cb = io->base.completion_cb;
         completion_data = io->base.completion_data;
@@ -85,7 +85,7 @@ soup_client_message_io_http1_finished (SoupClientMessageIO *iface)
         else
                 completion = SOUP_MESSAGE_IO_INTERRUPTED;
 
-        msg = g_object_ref (io->item->msg);
+        msg = g_object_ref (msg);
         soup_connection_message_io_finished (soup_message_get_connection (msg), msg);
         if (completion_cb)
                 completion_cb (G_OBJECT (msg), completion, completion_data);
@@ -734,7 +734,7 @@ io_run_until (SoupClientMessageIOHTTP1 *client_io,
               GError                  **error)
 {
         SoupMessageIOData *io = &client_io->base;
-        SoupMessage *msg = client_io->item->msg;
+        SoupMessage *msg;
         gboolean progress = TRUE, done;
         GError *my_error = NULL;
 
@@ -747,6 +747,8 @@ io_run_until (SoupClientMessageIOHTTP1 *client_io,
                 return FALSE;
         }
 
+        g_assert (client_io); // Silence clang static analysis
+        msg = client_io->item->msg;
         g_object_ref (msg);
 
         while (progress && (SoupClientMessageIOHTTP1 *)soup_message_get_io_data (msg) == client_io &&
@@ -841,22 +843,22 @@ soup_message_io_finish (SoupMessage  *msg,
         soup_message_io_finished (msg);
 }
 
-static void soup_client_message_io_http1_run (SoupClientMessageIO *iface, gboolean blocking);
+static void soup_client_message_io_http1_run (SoupClientMessageIO *iface, SoupMessage *msg, gboolean 
blocking);
 
 static gboolean
 io_run_ready (SoupMessage *msg, gpointer user_data)
 {
-        soup_client_message_io_http1_run (soup_message_get_io_data (msg), FALSE);
+        soup_client_message_io_http1_run (soup_message_get_io_data (msg), msg, FALSE);
         return FALSE;
 }
 
 static void
 soup_client_message_io_http1_run (SoupClientMessageIO *iface,
+                                  SoupMessage         *msg,
                                   gboolean             blocking)
 {
         SoupClientMessageIOHTTP1 *client_io = (SoupClientMessageIOHTTP1 *)iface;
         SoupMessageIOData *io = &client_io->base;
-        SoupMessage *msg = client_io->item->msg;
         GError *error = NULL;
 
         if (io->io_source) {
@@ -893,11 +895,11 @@ soup_client_message_io_http1_run (SoupClientMessageIO *iface,
 
 static gboolean
 soup_client_message_io_http1_run_until_read (SoupClientMessageIO *iface,
+                                             SoupMessage         *msg,
                                              GCancellable        *cancellable,
                                              GError             **error)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
-        SoupMessage *msg = io->item->msg;
 
         if (io_run_until (io, TRUE,
                           SOUP_MESSAGE_IO_STATE_BODY,
@@ -966,13 +968,13 @@ io_run_until_read_async (SoupClientMessageIOHTTP1 *client_io,
 
 static void
 soup_client_message_io_http1_run_until_read_async (SoupClientMessageIO *iface,
+                                                   SoupMessage         *msg,
                                                    int                  io_priority,
                                                    GCancellable        *cancellable,
                                                    GAsyncReadyCallback  callback,
                                                    gpointer             user_data)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
-        SoupMessage *msg = io->item->msg;
         GTask *task;
 
         task = g_task_new (msg, cancellable, callback, user_data);
@@ -982,12 +984,12 @@ soup_client_message_io_http1_run_until_read_async (SoupClientMessageIO *iface,
 
 static gboolean
 soup_client_message_io_http1_run_until_finish (SoupClientMessageIO *iface,
+                                               SoupMessage         *msg,
                                                gboolean             blocking,
                                                GCancellable        *cancellable,
                                                GError             **error)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
-        SoupMessage *msg = io->item->msg;
         gboolean success;
 
         g_object_ref (msg);
@@ -1016,10 +1018,10 @@ client_stream_eof (SoupClientInputStream    *stream,
 
 static GInputStream *
 soup_client_message_io_http1_get_response_stream (SoupClientMessageIO *iface,
+                                                  SoupMessage         *msg,
                                                   GError             **error)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
-        SoupMessage *msg = io->item->msg;
         GInputStream *client_stream;
 
         client_stream = soup_client_input_stream_new (io->base.body_istream, msg);
@@ -1054,7 +1056,8 @@ soup_client_message_io_http1_send_item (SoupClientMessageIO       *iface,
 }
 
 static void
-soup_client_message_io_http1_pause (SoupClientMessageIO *iface)
+soup_client_message_io_http1_pause (SoupClientMessageIO *iface,
+                                    SoupMessage         *msg)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
 
@@ -1064,7 +1067,8 @@ soup_client_message_io_http1_pause (SoupClientMessageIO *iface)
 }
 
 static void
-soup_client_message_io_http1_unpause (SoupClientMessageIO *iface)
+soup_client_message_io_http1_unpause (SoupClientMessageIO *iface,
+                                      SoupMessage         *msg)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
 
@@ -1073,13 +1077,45 @@ soup_client_message_io_http1_unpause (SoupClientMessageIO *iface)
 }
 
 static gboolean
-soup_client_message_io_http1_is_paused (SoupClientMessageIO *iface)
+soup_client_message_io_http1_is_paused (SoupClientMessageIO *iface,
+                                        SoupMessage         *msg)
 {
         SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
 
         return io->base.paused;
 }
 
+static gboolean
+soup_client_message_io_http1_in_progress (SoupClientMessageIO *iface,
+                                          SoupMessage         *msg)
+{
+        // In progress as long as object is alive
+        return TRUE;
+}
+
+static gboolean
+soup_client_message_io_http1_is_open (SoupClientMessageIO *iface)
+{
+        /* This is handled in SoupConnection */
+        g_assert_not_reached ();
+        return FALSE;
+}
+
+static gboolean
+soup_client_message_io_http1_is_reusable (SoupClientMessageIO *iface)
+{
+        SoupClientMessageIOHTTP1 *io = (SoupClientMessageIOHTTP1 *)iface;
+        return soup_message_is_keepalive (io->item->msg);
+}
+
+static void
+soup_client_message_io_http1_skip_body (SoupClientMessageIO *iface,
+                                       SoupMessage         *msg)
+{
+        /* This is already handled by SoupSession reading the rest
+         * of the stream */
+}
+
 static const SoupClientMessageIOFuncs io_funcs = {
         soup_client_message_io_http1_destroy,
         soup_client_message_io_http1_finished,
@@ -1092,7 +1128,11 @@ static const SoupClientMessageIOFuncs io_funcs = {
         soup_client_message_io_http1_run,
         soup_client_message_io_http1_run_until_read,
         soup_client_message_io_http1_run_until_read_async,
-        soup_client_message_io_http1_run_until_finish
+        soup_client_message_io_http1_run_until_finish,
+        soup_client_message_io_http1_in_progress,
+        soup_client_message_io_http1_skip_body,
+        soup_client_message_io_http1_is_open,
+        soup_client_message_io_http1_is_reusable,
 };
 
 SoupClientMessageIO *
diff --git a/libsoup/soup-client-message-io.c b/libsoup/soup-client-message-io.c
index 1db0c2e3..3537469d 100644
--- a/libsoup/soup-client-message-io.c
+++ b/libsoup/soup-client-message-io.c
@@ -19,9 +19,10 @@ soup_client_message_io_destroy (SoupClientMessageIO *io)
 }
 
 void
-soup_client_message_io_finished (SoupClientMessageIO *io)
+soup_client_message_io_finished (SoupClientMessageIO *io,
+                                 SoupMessage         *msg)
 {
-        io->funcs->finished (io);
+        io->funcs->finished (io, msg);
 }
 
 void
@@ -40,60 +41,94 @@ soup_client_message_io_send_item (SoupClientMessageIO       *io,
 }
 
 void
-soup_client_message_io_pause (SoupClientMessageIO *io)
+soup_client_message_io_pause (SoupClientMessageIO *io,
+                              SoupMessage         *msg)
 {
-        io->funcs->pause (io);
+        io->funcs->pause (io, msg);
 }
 
 void
-soup_client_message_io_unpause (SoupClientMessageIO *io)
+soup_client_message_io_unpause (SoupClientMessageIO *io,
+                                SoupMessage         *msg)
 {
-        io->funcs->unpause (io);
+        io->funcs->unpause (io, msg);
 }
 
 gboolean
-soup_client_message_io_is_paused (SoupClientMessageIO *io)
+soup_client_message_io_is_paused (SoupClientMessageIO *io,
+                                  SoupMessage         *msg)
 {
-        return io->funcs->is_paused (io);
+        return io->funcs->is_paused (io, msg);
 }
 
 void
 soup_client_message_io_run (SoupClientMessageIO *io,
+                            SoupMessage         *msg,
                             gboolean             blocking)
 {
-        io->funcs->run (io, blocking);
+        io->funcs->run (io, msg, blocking);
 }
 
 gboolean
 soup_client_message_io_run_until_read (SoupClientMessageIO *io,
+                                       SoupMessage         *msg,
                                        GCancellable        *cancellable,
                                        GError             **error)
 {
-        return io->funcs->run_until_read (io, cancellable, error);
+        return io->funcs->run_until_read (io, msg, cancellable, error);
 }
 
 void
 soup_client_message_io_run_until_read_async (SoupClientMessageIO *io,
+                                             SoupMessage         *msg,
                                              int                  io_priority,
                                              GCancellable        *cancellable,
                                              GAsyncReadyCallback  callback,
                                              gpointer             user_data)
 {
-        io->funcs->run_until_read_async (io, io_priority, cancellable, callback, user_data);
+        io->funcs->run_until_read_async (io, msg, io_priority, cancellable, callback, user_data);
 }
 
 gboolean
 soup_client_message_io_run_until_finish (SoupClientMessageIO *io,
+                                         SoupMessage         *msg,
                                          gboolean             blocking,
                                          GCancellable        *cancellable,
                                          GError             **error)
 {
-        return io->funcs->run_until_finish (io, blocking, cancellable, error);
+        return io->funcs->run_until_finish (io, msg, blocking, cancellable, error);
 }
 
 GInputStream *
 soup_client_message_io_get_response_stream (SoupClientMessageIO *io,
+                                            SoupMessage         *msg,
                                             GError             **error)
 {
-        return io->funcs->get_response_stream (io, error);
+        return io->funcs->get_response_stream (io, msg, error);
+}
+
+gboolean
+soup_client_message_io_in_progress (SoupClientMessageIO       *io,
+                                    SoupMessage               *msg)
+{
+        return io->funcs->in_progress (io, msg);
+}
+
+gboolean
+soup_client_message_io_is_open (SoupClientMessageIO       *io)
+{
+        return io->funcs->is_open (io);
+}
+
+gboolean
+soup_client_message_io_is_reusable (SoupClientMessageIO       *io)
+{
+        return io->funcs->is_reusable (io);
+}
+
+void
+soup_client_message_io_skip_body (SoupClientMessageIO       *io,
+                                  SoupMessage               *msg)
+{
+        io->funcs->skip_body (io, msg);
 }
diff --git a/libsoup/soup-client-message-io.h b/libsoup/soup-client-message-io.h
index 22f9bc47..c589354d 100644
--- a/libsoup/soup-client-message-io.h
+++ b/libsoup/soup-client-message-io.h
@@ -12,31 +12,46 @@ typedef struct _SoupClientMessageIO SoupClientMessageIO;
 
 typedef struct {
         void          (*destroy)              (SoupClientMessageIO       *io);
-        void          (*finished)             (SoupClientMessageIO       *io);
+        void          (*finished)             (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg);
         void          (*stolen)               (SoupClientMessageIO       *io);
         void          (*send_item)            (SoupClientMessageIO       *io,
                                                SoupMessageQueueItem      *item,
                                                SoupMessageIOCompletionFn  completion_cb,
                                                gpointer                   user_data);
         GInputStream *(*get_response_stream)  (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg,
                                                GError                   **error);
-        void          (*pause)                (SoupClientMessageIO       *io);
-        void          (*unpause)              (SoupClientMessageIO       *io);
-        gboolean      (*is_paused)            (SoupClientMessageIO       *io);
+        void          (*pause)                (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg);
+        void          (*unpause)              (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg);
+        gboolean      (*is_paused)            (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg);
         void          (*run)                  (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg,
                                                gboolean                   blocking);
         gboolean      (*run_until_read)       (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg,
                                                GCancellable              *cancellable,
                                                GError                   **error);
         void          (*run_until_read_async) (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg,
                                                int                        io_priority,
                                                GCancellable              *cancellable,
                                                GAsyncReadyCallback        callback,
                                                gpointer                   user_data);
         gboolean      (*run_until_finish)     (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg,
                                                gboolean                   blocking,
                                                GCancellable              *cancellable,
                                                GError                   **error);
+        gboolean      (*in_progress)          (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg);
+        void          (*skip_body)            (SoupClientMessageIO       *io,
+                                               SoupMessage               *msg);
+        gboolean      (*is_open)              (SoupClientMessageIO       *io);
+        gboolean      (*is_reusable)          (SoupClientMessageIO       *io);
 } SoupClientMessageIOFuncs;
 
 struct _SoupClientMessageIO {
@@ -44,28 +59,43 @@ struct _SoupClientMessageIO {
 };
 
 void          soup_client_message_io_destroy              (SoupClientMessageIO       *io);
-void          soup_client_message_io_finished             (SoupClientMessageIO       *io);
+void          soup_client_message_io_finished             (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg);
 void          soup_client_message_io_stolen               (SoupClientMessageIO       *io);
 void          soup_client_message_io_send_item            (SoupClientMessageIO       *io,
                                                            SoupMessageQueueItem      *item,
                                                            SoupMessageIOCompletionFn  completion_cb,
                                                            gpointer                   user_data);
-void          soup_client_message_io_pause                (SoupClientMessageIO       *io);
-void          soup_client_message_io_unpause              (SoupClientMessageIO       *io);
-gboolean      soup_client_message_io_is_paused            (SoupClientMessageIO       *io);
+void          soup_client_message_io_pause                (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg);
+void          soup_client_message_io_unpause              (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg);
+gboolean      soup_client_message_io_is_paused            (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg);
 void          soup_client_message_io_run                  (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg,
                                                            gboolean                   blocking);
 gboolean      soup_client_message_io_run_until_read       (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg,
                                                            GCancellable              *cancellable,
                                                            GError                   **error);
 void          soup_client_message_io_run_until_read_async (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg,
                                                            int                        io_priority,
                                                            GCancellable              *cancellable,
                                                            GAsyncReadyCallback        callback,
                                                            gpointer                   user_data);
 gboolean      soup_client_message_io_run_until_finish     (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg,
                                                            gboolean                   blocking,
                                                            GCancellable              *cancellable,
                                                            GError                   **error);
 GInputStream *soup_client_message_io_get_response_stream  (SoupClientMessageIO       *io,
-                                                          GError                    **error);
+                                                           SoupMessage               *msg,
+                                                           GError                   **error);
+gboolean      soup_client_message_io_in_progress           (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg);
+gboolean      soup_client_message_io_is_open              (SoupClientMessageIO       *io);
+gboolean      soup_client_message_io_is_reusable          (SoupClientMessageIO       *io);
+void          soup_client_message_io_skip_body            (SoupClientMessageIO       *io,
+                                                           SoupMessage               *msg);
\ No newline at end of file
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index a7666246..e64d5015 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -14,6 +14,7 @@
 #include "soup-io-stream.h"
 #include "soup-message-queue-item.h"
 #include "soup-client-message-io-http1.h"
+#include "soup-client-message-io-http2.h"
 #include "soup-socket-properties.h"
 #include "soup-private-enum-types.h"
 #include <gio/gnetworking.h>
@@ -29,6 +30,7 @@ typedef struct {
        SoupSocketProperties *socket_props;
         guint64 id;
         GSocketAddress *remote_address;
+        gboolean force_http1;
 
        GUri *proxy_uri;
        gboolean ssl;
@@ -39,6 +41,7 @@ typedef struct {
        time_t       unused_timeout;
        GSource     *idle_timeout_src;
        gboolean     reusable;
+        SoupHTTPVersion http_version;
 
        GCancellable *cancellable;
 } SoupConnectionPrivate;
@@ -65,6 +68,7 @@ enum {
        PROP_SSL,
        PROP_TLS_CERTIFICATE,
        PROP_TLS_CERTIFICATE_ERRORS,
+        PROP_FORCE_HTTP1,
 
        LAST_PROPERTY
 };
@@ -81,6 +85,9 @@ static void stop_idle_timer (SoupConnectionPrivate *priv);
 static void
 soup_connection_init (SoupConnection *conn)
 {
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        priv->http_version = SOUP_HTTP_1_1;
 }
 
 static void
@@ -144,6 +151,9 @@ soup_connection_set_property (GObject *object, guint prop_id,
        case PROP_ID:
                priv->id = g_value_get_uint64 (value);
                break;
+       case PROP_FORCE_HTTP1:
+               priv->force_http1 = g_value_get_boolean (value);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -181,6 +191,9 @@ soup_connection_get_property (GObject *object, guint prop_id,
        case PROP_TLS_CERTIFICATE_ERRORS:
                g_value_set_flags (value, soup_connection_get_tls_certificate_errors (SOUP_CONNECTION 
(object)));
                break;
+       case PROP_FORCE_HTTP1:
+               g_value_set_boolean (value, priv->force_http1);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -284,6 +297,12 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
                                     G_TYPE_TLS_CERTIFICATE_FLAGS, 0,
                                     G_PARAM_READABLE |
                                     G_PARAM_STATIC_STRINGS);
+        properties[PROP_FORCE_HTTP1] =
+                g_param_spec_boolean ("force-http1",
+                                      "Force HTTP 1.x",
+                                      "Force connection to use HTTP 1.x",
+                                      FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                      G_PARAM_STATIC_STRINGS);
 
         g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
 }
@@ -345,7 +364,7 @@ current_msg_got_body (SoupMessage *msg, gpointer user_data)
                g_clear_pointer (&priv->proxy_uri, g_uri_unref);
        }
 
-       priv->reusable = soup_message_is_keepalive (msg);
+       priv->reusable = soup_client_message_io_is_reusable (priv->io_data);
 }
 
 static void
@@ -368,9 +387,20 @@ set_current_msg (SoupConnection *conn, SoupMessage *msg)
 
        g_return_if_fail (priv->state == SOUP_CONNECTION_IN_USE);
 
+        /* With HTTP/1.x we keep track of the current message both for
+         * proxying and to find out later if the connection is reusable
+         * with keep-alive. With HTTP/2 we don't support proxying and
+         * we assume its reusable by default and detect a closed connection
+         * elsewhere */
+        if (priv->http_version >= SOUP_HTTP_2_0) {
+                priv->reusable = TRUE;
+                return;
+        }
+
        g_object_freeze_notify (G_OBJECT (conn));
 
        if (priv->current_msg) {
+                // FIXME
                g_return_if_fail (soup_message_get_method (priv->current_msg) == SOUP_METHOD_CONNECT);
                clear_current_msg (conn);
        }
@@ -467,6 +497,15 @@ new_tls_connection (SoupConnection    *conn,
 {
         SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
         GTlsClientConnection *tls_connection;
+        GPtrArray *advertised_protocols = g_ptr_array_sized_new (4);
+
+        // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
+        if (!priv->force_http1)
+                g_ptr_array_add (advertised_protocols, "h2");
+
+        g_ptr_array_add (advertised_protocols, "http/1.1");
+        g_ptr_array_add (advertised_protocols, "http/1.0");
+        g_ptr_array_add (advertised_protocols, NULL);
 
         tls_connection = g_initable_new (g_tls_backend_get_client_connection_type (g_tls_backend_get_default 
()),
                                          priv->cancellable, error,
@@ -474,7 +513,11 @@ new_tls_connection (SoupConnection    *conn,
                                          "server-identity", priv->remote_connectable,
                                          "require-close-notify", FALSE,
                                          "interaction", priv->socket_props->tls_interaction,
+                                         "advertised-protocols", advertised_protocols->pdata,
                                          NULL);
+
+        g_ptr_array_unref (advertised_protocols);
+
         if (!tls_connection)
                 return NULL;
 
@@ -543,6 +586,15 @@ soup_connection_complete (SoupConnection *conn)
 
         g_clear_object (&priv->cancellable);
 
+        if (priv->ssl) {
+                const char *protocol = g_tls_connection_get_negotiated_protocol (G_TLS_CONNECTION 
(priv->connection));
+                if (g_strcmp0 (protocol, "h2") == 0)
+                        priv->http_version = SOUP_HTTP_2_0;
+                else if (g_strcmp0 (protocol, "http/1.0") == 0)
+                        priv->http_version = SOUP_HTTP_1_0;
+                // Default is 1.1
+        }
+
         if (!priv->ssl || !priv->proxy_uri) {
                 soup_connection_event (conn,
                                        G_SOCKET_CLIENT_COMPLETE,
@@ -937,9 +989,7 @@ static gboolean
 is_idle_connection_disconnected (SoupConnection *conn)
 {
        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
-       GInputStream *istream;
        char buffer[1];
-       GError *error = NULL;
 
        if (!g_socket_is_connected (soup_connection_get_socket (conn)))
                return TRUE;
@@ -947,26 +997,33 @@ is_idle_connection_disconnected (SoupConnection *conn)
        if (priv->unused_timeout && priv->unused_timeout < time (NULL))
                return TRUE;
 
-       istream = g_io_stream_get_input_stream (priv->iostream);
-
-       /* This is tricky. The goal is to check if the socket is readable. If
-        * so, that means either the server has disconnected or it's broken (it
-        * should not send any data while the connection is in idle state). But
-        * we can't just check the readability of the SoupSocket because there
-        * could be non-application layer TLS data that is readable, but which
-        * we don't want to consider. So instead, just read and see if the read
-        * succeeds. This is OK to do here because if the read does succeed, we
-        * just disconnect and ignore the data anyway.
-        */
-       g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (istream),
-                                                 &buffer, sizeof (buffer),
-                                                 NULL, &error);
-       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
-               g_clear_error (&error);
-               return TRUE;
-       }
+        if (priv->http_version == SOUP_HTTP_2_0) {
+                /* We need the HTTP2 backend to maintain its read state.
+                 * It does exactly what below does in practice */
+                return !soup_client_message_io_is_open (priv->io_data);
+        } else {
+                GInputStream *istream = g_io_stream_get_input_stream (priv->iostream);
+                GError *error = NULL;
+
+                /* This is tricky. The goal is to check if the socket is readable. If
+                * so, that means either the server has disconnected or it's broken (it
+                * should not send any data while the connection is in idle state). But
+                * we can't just check the readability of the SoupSocket because there
+                * could be non-application layer TLS data that is readable, but which
+                * we don't want to consider. So instead, just read and see if the read
+                * succeeds. This is OK to do here because if the read does succeed, we
+                * just disconnect and ignore the data anyway.
+                */
+                g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (istream),
+                                                        &buffer, sizeof (buffer),
+                                                        NULL, &error);
+                if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                        g_clear_error (&error);
+                        return TRUE;
+                }
 
-       g_error_free (error);
+                g_error_free (error);
+        }
 
        return FALSE;
 }
@@ -1058,8 +1115,13 @@ soup_connection_setup_message_io (SoupConnection *conn,
         else
                 priv->reusable = FALSE;
 
-        g_assert (priv->io_data == NULL);
-        priv->io_data = soup_client_message_io_http1_new (priv->iostream);
+        if (priv->http_version <= SOUP_HTTP_1_1) {
+                g_assert (priv->io_data == NULL);
+                priv->io_data = soup_client_message_io_http1_new (priv->iostream);
+        } else {
+                if (!priv->io_data || !soup_client_message_io_is_reusable (priv->io_data))
+                        priv->io_data = soup_client_message_io_http2_new (priv->iostream);
+        }
 
         return priv->io_data;
 }
@@ -1070,8 +1132,10 @@ soup_connection_message_io_finished (SoupConnection *conn,
 {
         SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
 
-        g_assert (priv->current_msg == msg);
-        g_clear_pointer (&priv->io_data, soup_client_message_io_destroy);
+        if (priv->http_version < SOUP_HTTP_2_0) {
+                g_assert (priv->current_msg == msg);
+                g_clear_pointer (&priv->io_data, soup_client_message_io_destroy);
+        }
 }
 
 GTlsCertificate *
@@ -1119,3 +1183,19 @@ soup_connection_get_remote_address (SoupConnection *conn)
 
         return priv->remote_address;
 }
+
+SoupHTTPVersion
+soup_connection_get_negotiated_protocol (SoupConnection *conn)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        return priv->http_version;
+}
+
+gboolean
+soup_connection_is_reusable (SoupConnection *conn)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        return priv->reusable;
+}
diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h
index 9c556029..bb076037 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -75,6 +75,8 @@ GTlsCertificateFlags soup_connection_get_tls_certificate_errors (SoupConnection
 
 guint64              soup_connection_get_id                     (SoupConnection *conn);
 GSocketAddress      *soup_connection_get_remote_address         (SoupConnection *conn);
+SoupHTTPVersion      soup_connection_get_negotiated_protocol    (SoupConnection *conn);
+gboolean             soup_connection_is_reusable                (SoupConnection *conn);
 
 G_END_DECLS
 
diff --git a/libsoup/soup-logger-private.h b/libsoup/soup-logger-private.h
index a30eb396..e48d16c0 100644
--- a/libsoup/soup-logger-private.h
+++ b/libsoup/soup-logger-private.h
@@ -13,4 +13,6 @@ void soup_logger_request_body_setup (SoupLogger           *logger,
                                      SoupMessage          *msg,
                                      SoupBodyOutputStream *stream);
 
+void soup_logger_log_request_data (SoupLogger *logger, SoupMessage *msg, const char *buffer, gsize len);
+
 G_END_DECLS
diff --git a/libsoup/soup-logger.c b/libsoup/soup-logger.c
index 0c56d797..e3963c52 100644
--- a/libsoup/soup-logger.c
+++ b/libsoup/soup-logger.c
@@ -178,6 +178,14 @@ write_body (SoupLogger *logger, const char *buffer, gsize nread,
         }
 }
 
+void
+soup_logger_log_request_data (SoupLogger *logger, SoupMessage *msg, const char *buffer, gsize len)
+{
+        SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+
+        write_body (logger, buffer, len, msg, priv->request_bodies);
+}
+
 static void
 write_response_body (SoupLoggerInputStream *stream, char *buffer, gsize nread,
                      gpointer user_data)
@@ -649,17 +657,17 @@ print_request (SoupLogger *logger, SoupMessage *msg,
        uri = soup_message_get_uri (msg);
        if (soup_message_get_method (msg) == SOUP_METHOD_CONNECT) {
                soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
-                                  "CONNECT %s:%u HTTP/1.%d",
+                                  "CONNECT %s:%u HTTP/%s",
                                   g_uri_get_host (uri), g_uri_get_port (uri),
-                                  soup_message_get_http_version (msg));
+                                  soup_http_version_to_string (soup_message_get_http_version (msg)));
        } else {
                soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
-                                  "%s %s%s%s HTTP/1.%d",
+                                  "%s %s%s%s HTTP/%s",
                                   soup_message_get_method (msg),
                                    g_uri_get_path (uri),
                                   g_uri_get_query (uri) ? "?" : "",
                                   g_uri_get_query (uri) ? g_uri_get_query (uri) : "",
-                                  soup_message_get_http_version (msg));
+                                  soup_http_version_to_string (soup_message_get_http_version (msg)));
        }
 
        soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
@@ -729,8 +737,8 @@ print_response (SoupLogger *logger, SoupMessage *msg)
                return;
 
        soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
-                          "HTTP/1.%d %u %s\n",
-                          soup_message_get_http_version (msg),
+                          "HTTP/%s %u %s\n",
+                          soup_http_version_to_string (soup_message_get_http_version (msg)),
                           soup_message_get_status (msg), soup_message_get_reason_phrase (msg));
 
        soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index b5c177dd..5b51e4d9 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -1499,6 +1499,7 @@ soup_message_cleanup_response (SoupMessage *msg)
  *   be emitted, if you want to disable authentication for a message use
  *   soup_message_disable_feature() passing #SOUP_TYPE_AUTH_MANAGER instead.
  * @SOUP_MESSAGE_COLLECT_METRICS: Metrics will be collected for this message.
+ * @SOUP_MESSAGE_FORCE_HTTP1: Require an HTTP/1.x connection.
  *
  * Various flags that can be set on a #SoupMessage to alter its
  * behavior.
@@ -2187,7 +2188,7 @@ soup_message_io_finished (SoupMessage *msg)
                 return;
 
         g_assert (priv->connection != NULL);
-        soup_client_message_io_finished (g_steal_pointer (&priv->io_data));
+        soup_client_message_io_finished (g_steal_pointer (&priv->io_data), msg);
 }
 
 void
@@ -2197,7 +2198,7 @@ soup_message_io_pause (SoupMessage *msg)
 
         g_return_if_fail (priv->io_data != NULL);
 
-        soup_client_message_io_pause (priv->io_data);
+        soup_client_message_io_pause (priv->io_data, msg);
 }
 
 void
@@ -2207,7 +2208,7 @@ soup_message_io_unpause (SoupMessage *msg)
 
         g_return_if_fail (priv->io_data != NULL);
 
-        soup_client_message_io_unpause (priv->io_data);
+        soup_client_message_io_unpause (priv->io_data, msg);
 }
 
 gboolean
@@ -2215,7 +2216,7 @@ soup_message_is_io_paused (SoupMessage *msg)
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        return priv->io_data && soup_client_message_io_is_paused (priv->io_data);
+        return priv->io_data && soup_client_message_io_is_paused (priv->io_data, msg);
 }
 
 /**
@@ -2231,7 +2232,7 @@ soup_message_io_in_progress (SoupMessage *msg)
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        return priv->io_data != NULL;
+        return priv->io_data && soup_client_message_io_in_progress (priv->io_data, msg);
 }
 
 void
@@ -2240,7 +2241,7 @@ soup_message_io_run (SoupMessage *msg,
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        soup_client_message_io_run (priv->io_data, blocking);
+        soup_client_message_io_run (priv->io_data, msg, blocking);
 }
 
 gboolean
@@ -2250,7 +2251,7 @@ soup_message_io_run_until_read (SoupMessage  *msg,
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        return soup_client_message_io_run_until_read (priv->io_data, cancellable, error);
+        return soup_client_message_io_run_until_read (priv->io_data, msg, cancellable, error);
 }
 
 void
@@ -2262,7 +2263,7 @@ soup_message_io_run_until_read_async (SoupMessage        *msg,
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        soup_client_message_io_run_until_read_async (priv->io_data, io_priority, cancellable, callback, 
user_data);
+        soup_client_message_io_run_until_read_async (priv->io_data, msg, io_priority, cancellable, callback, 
user_data);
 }
 
 gboolean
@@ -2281,7 +2282,7 @@ soup_message_io_run_until_finish (SoupMessage  *msg,
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        return soup_client_message_io_run_until_finish (priv->io_data, blocking, cancellable, error);
+        return soup_client_message_io_run_until_finish (priv->io_data, msg, blocking, cancellable, error);
 }
 
 void
@@ -2303,7 +2304,7 @@ soup_message_io_get_response_istream (SoupMessage  *msg,
 {
         SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
-        return soup_client_message_io_get_response_stream (priv->io_data, error);
+        return soup_client_message_io_get_response_stream (priv->io_data, msg, error);
 }
 
 SoupContentSniffer *
diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h
index 93fd9af0..e11ec2d8 100644
--- a/libsoup/soup-message.h
+++ b/libsoup/soup-message.h
@@ -78,7 +78,8 @@ typedef enum {
        SOUP_MESSAGE_NEW_CONNECTION           = (1 << 2),
        SOUP_MESSAGE_IDEMPOTENT               = (1 << 3),
        SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE    = (1 << 4),
-        SOUP_MESSAGE_COLLECT_METRICS          = (1 << 5)
+        SOUP_MESSAGE_COLLECT_METRICS          = (1 << 5),
+        SOUP_MESSAGE_FORCE_HTTP1              = (1 << 6)
 } SoupMessageFlags;
 
 SOUP_AVAILABLE_IN_ALL
diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c
index 76972cbf..c0712928 100644
--- a/libsoup/soup-misc.c
+++ b/libsoup/soup-misc.c
@@ -277,3 +277,19 @@ soup_get_accept_languages_from_system (void)
 
        return langs_str;
 }
+
+const char *
+soup_http_version_to_string (SoupHTTPVersion version)
+{
+        switch (version) {
+        case SOUP_HTTP_1_0:
+                return "1.0";
+        case SOUP_HTTP_1_1:
+                return "1.1";
+        case SOUP_HTTP_2_0:
+                return "2";
+        }
+
+        g_warn_if_reached ();
+        return "Unknown";
+}
\ No newline at end of file
diff --git a/libsoup/soup-misc.h b/libsoup/soup-misc.h
index c9722fea..656f05b5 100644
--- a/libsoup/soup-misc.h
+++ b/libsoup/soup-misc.h
@@ -74,6 +74,8 @@ gboolean           soup_host_matches_host    (const gchar *host,
 
 gchar *soup_get_accept_languages_from_system (void);
 
+const char *soup_http_version_to_string (SoupHTTPVersion version);
+
 G_END_DECLS
 
 #endif /* __SOUP_MISC_H__ */
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 999b8306..81bc43a8 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -1264,17 +1264,19 @@ soup_session_redirect_message (SoupSession *session,
                                                   SOUP_ENCODING_NONE);
        }
 
-       host = soup_uri_get_host_for_headers (new_uri);
-       if (soup_uri_uses_default_port (new_uri))
-               soup_message_headers_replace (soup_message_get_request_headers (msg), "Host", host);
-       else {
-               char *value;
+        if (soup_message_get_http_version (msg) < SOUP_HTTP_2_0) {
+                host = soup_uri_get_host_for_headers (new_uri);
+                if (soup_uri_uses_default_port (new_uri))
+                        soup_message_headers_replace (soup_message_get_request_headers (msg), "Host", host);
+                else {
+                        char *value;
 
-               value = g_strdup_printf ("%s:%d", host, g_uri_get_port (new_uri));
-               soup_message_headers_replace (soup_message_get_request_headers (msg), "Host", value);
-               g_free (value);
-       }
-       g_free (host);
+                        value = g_strdup_printf ("%s:%d", host, g_uri_get_port (new_uri));
+                        soup_message_headers_replace (soup_message_get_request_headers (msg), "Host", value);
+                        g_free (value);
+                }
+                g_free (host);
+        }
 
        soup_message_set_uri (msg, new_uri);
        g_uri_unref (new_uri);
@@ -1298,6 +1300,23 @@ redirect_handler (SoupMessage *msg,
        soup_session_redirect_message (session, msg, &item->error);
 }
 
+static void
+misdirected_handler (SoupMessage *msg,
+                    gpointer     user_data)
+{
+       SoupMessageQueueItem *item = user_data;
+       SoupSession *session = item->session;
+
+        /* HTTP/2 messages may get the misdirected request status and MAY
+         * try a new connection */
+        if (!soup_message_query_flags (msg, SOUP_MESSAGE_NEW_CONNECTION)) {
+                soup_message_add_flags (msg, SOUP_MESSAGE_NEW_CONNECTION);
+                soup_session_requeue_item (session,
+                                         item,
+                                         &item->error);
+        }
+}
+
 static void
 message_restarted (SoupMessage *msg, gpointer user_data)
 {
@@ -1351,6 +1370,9 @@ soup_session_append_queue_item (SoupSession        *session,
                        msg, "got_body", "Location",
                        G_CALLBACK (redirect_handler), item);
        }
+        soup_message_add_status_code_handler (msg, "got-body",
+                                              SOUP_STATUS_MISDIRECTED_REQUEST,
+                                              G_CALLBACK (misdirected_handler), item);
        g_signal_connect (msg, "restarted",
                          G_CALLBACK (message_restarted), item);
 
@@ -1381,18 +1403,24 @@ soup_session_send_queue_item (SoupSession *session,
        if (priv->accept_language && !soup_message_headers_get_list (request_headers, "Accept-Language"))
                soup_message_headers_append (request_headers, "Accept-Language", priv->accept_language);
 
+        soup_message_set_http_version (item->msg, soup_connection_get_negotiated_protocol 
(soup_message_get_connection (item->msg)));
+
+        // TODO: Move these to the HTTP1 backend?
+
        /* Force keep alive connections for HTTP 1.0. Performance will
         * improve when issuing multiple requests to the same host in
         * a short period of time, as we wouldn't need to establish
         * new connections. Keep alive is implicit for HTTP 1.1.
         */
-       if (!soup_message_headers_header_contains (request_headers, "Connection", "Keep-Alive") &&
+       if (soup_message_get_http_version (item->msg) <= SOUP_HTTP_1_1 &&
+            !soup_message_headers_header_contains (request_headers, "Connection", "Keep-Alive") &&
            !soup_message_headers_header_contains (request_headers, "Connection", "close") &&
            !soup_message_headers_header_contains (request_headers, "Connection", "Upgrade")) {
                soup_message_headers_append (request_headers, "Connection", "Keep-Alive");
        }
 
-        if (!soup_message_headers_get_one (request_headers, "Host")) {
+        if (soup_message_get_http_version (item->msg) < SOUP_HTTP_2_0 &&
+            !soup_message_headers_get_one (request_headers, "Host")) {
                 GUri *uri = soup_message_get_uri (item->msg);
                 char *host;
 
@@ -1797,9 +1825,9 @@ get_connection_for_host (SoupSession *session,
 {
        SoupSessionPrivate *priv = soup_session_get_instance_private (session);
        GSocketConnectable *remote_connectable;
+        gboolean force_http1;
        SoupConnection *conn;
        GSList *conns;
-       guint num_pending = 0;
 
        if (priv->disposed)
                return NULL;
@@ -1810,10 +1838,22 @@ get_connection_for_host (SoupSession *session,
                return conn;
        }
 
+        force_http1 = soup_message_query_flags (item->msg, SOUP_MESSAGE_FORCE_HTTP1);
+        if (g_getenv ("SOUP_FORCE_HTTP1"))
+                force_http1 = TRUE;
+
        for (conns = host->connections; conns; conns = conns->next) {
                conn = conns->data;
 
+                if (force_http1 && soup_connection_get_negotiated_protocol (conn) > SOUP_HTTP_1_1)
+                        continue;
+
                switch (soup_connection_get_state (conn)) {
+                case SOUP_CONNECTION_IN_USE:
+                        if (!need_new_connection && soup_connection_is_reusable (conn)) {
+                                return conn;
+                        }
+                        break;
                case SOUP_CONNECTION_IDLE:
                        if (!need_new_connection) {
                                soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE);
@@ -1824,19 +1864,15 @@ get_connection_for_host (SoupSession *session,
                        if (steal_preconnection (session, item, conn))
                                return conn;
 
-                       num_pending++;
-                       break;
+                        /* Always wait if we have a pending connection as it may be
+                         * an h2 connection which will be shared. http/1.x connections
+                         * will only be slightly delayed. */
+                       return NULL;
                default:
                        break;
                }
        }
 
-       /* Limit the number of pending connections; num_messages / 2
-        * is somewhat arbitrary...
-        */
-       if (num_pending > host->num_messages / 2)
-               return NULL;
-
        if (host->num_conns >= priv->max_conns_per_host) {
                if (need_new_connection)
                        *try_cleanup = TRUE;
@@ -1865,6 +1901,7 @@ get_connection_for_host (SoupSession *session,
                             "remote-connectable", remote_connectable,
                             "ssl", soup_uri_is_https (host->uri),
                             "socket-properties", priv->socket_props,
+                             "force-http1", force_http1,
                             NULL);
        g_object_unref (remote_connectable);
 
@@ -2827,6 +2864,9 @@ expected_to_be_requeued (SoupSession *session, SoupMessage *msg)
                return !feature || !soup_message_disables_feature (msg, feature);
        }
 
+        if (soup_message_get_status (msg) == SOUP_STATUS_MISDIRECTED_REQUEST)
+                return TRUE;
+
        if (!soup_message_query_flags (msg, SOUP_MESSAGE_NO_REDIRECT)) {
                return SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg) ||
                        SOUP_SESSION_WOULD_REDIRECT_AS_SAFE (session, msg);
@@ -2953,6 +2993,10 @@ send_async_maybe_complete (SoupMessageQueueItem *item,
        if (expected_to_be_requeued (item->session, item->msg)) {
                GOutputStream *ostream;
 
+                /* Tell the backend to expect no more body */
+                soup_client_message_io_skip_body (soup_message_get_io_data (item->msg),
+                                                  item->msg);
+
                /* Gather the current message body... */
                ostream = g_memory_output_stream_new_resizable ();
                g_object_set_data_full (G_OBJECT (item->task), "SoupSession:ostream",
@@ -3355,6 +3399,8 @@ soup_session_send (SoupSession   *session,
                if (!expected_to_be_requeued (session, msg))
                        break;
 
+                // FIXME skip_body()
+
                /* Gather the current message body... */
                ostream = g_memory_output_stream_new_resizable ();
                if (g_output_stream_splice (ostream, stream,
@@ -3785,6 +3831,11 @@ soup_session_websocket_connect_async (SoupSession          *session,
         */
        soup_message_add_flags (msg, SOUP_MESSAGE_NEW_CONNECTION);
 
+        /* WebSocket negotiation over HTTP/2 is not currently supported
+         * and in practice all websocket servers support HTTP1.x with
+         * HTTP/2 not providing a tangible benefit */
+        soup_message_add_flags (msg, SOUP_MESSAGE_FORCE_HTTP1);
+
        item = soup_session_append_queue_item (session, msg, TRUE, cancellable);
        item->io_priority = io_priority;
 
diff --git a/libsoup/soup-status.c b/libsoup/soup-status.c
index 32a5fd7e..83f02d78 100644
--- a/libsoup/soup-status.c
+++ b/libsoup/soup-status.c
@@ -115,6 +115,7 @@
  * @SOUP_STATUS_INVALID_RANGE: shorter alias for
  * %SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE
  * @SOUP_STATUS_EXPECTATION_FAILED: 417 Expectation Failed (HTTP)
+ * @SOUP_STATUS_MISDIRECTED_REQUEST: 420 Misdirected Request
  * @SOUP_STATUS_UNPROCESSABLE_ENTITY: 422 Unprocessable Entity
  * (WebDAV)
  * @SOUP_STATUS_LOCKED: 423 Locked (WebDAV)
diff --git a/libsoup/soup-status.h b/libsoup/soup-status.h
index b72c140e..99c717f9 100644
--- a/libsoup/soup-status.h
+++ b/libsoup/soup-status.h
@@ -64,6 +64,7 @@ typedef enum {
        SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
        SOUP_STATUS_INVALID_RANGE                   = SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE,
        SOUP_STATUS_EXPECTATION_FAILED              = 417,
+        SOUP_STATUS_MISDIRECTED_REQUEST             = 421, /* HTTP/2 */
        SOUP_STATUS_UNPROCESSABLE_ENTITY            = 422, /* WebDAV */
        SOUP_STATUS_LOCKED                          = 423, /* WebDAV */
        SOUP_STATUS_FAILED_DEPENDENCY               = 424, /* WebDAV */
diff --git a/meson.build b/meson.build
index 157da832..3daca77b 100644
--- a/meson.build
+++ b/meson.build
@@ -100,6 +100,8 @@ gio_dep = dependency('gio-2.0', version : glib_required_version,
 
 glib_deps = [glib_dep, gobject_dep, gio_dep]
 
+libnghttp2_dep = dependency('libnghttp2')
+
 sqlite_dep = dependency('sqlite3', required: false)
 
 # Fallback check for sqlite, not all platforms ship pkg-config file
@@ -289,7 +291,20 @@ else
   have_autobahn = find_program('wstest', required: get_option('autobahn')).found()
 endif
 
-if not have_apache or not have_autobahn
+# Quart server used for HTTP/2 tests
+pymod = import('python')
+quart_found = false
+
+python = pymod.find_installation('python3')
+if python.found()
+  ret = run_command(python, '-c', 'import importlib\nassert(importlib.find_loader("quart"))')
+  if ret.returncode() == 0
+    quart_found = true
+  endif
+endif
+message('Python module quart found: @0@'.format(quart_found.to_string('YES', 'NO')))
+
+if not have_apache or not quart_found or not have_autobahn
   warning('Some regression tests will not be compiled due to missing libraries or modules. Please check the 
logs for more details.')
 endif
 
@@ -415,6 +430,7 @@ summary({
 summary({
     'All tests' : get_option('tests'),
     'Tests requiring Apache' : have_apache,
+    'Tests requiring Quart' : quart_found,
     'Fuzzing tests' : get_option('fuzzing').enabled(),
     'Autobahn tests' : have_autobahn,
     'Install tests': get_option('installed_tests'),
diff --git a/tests/http2-server.py b/tests/http2-server.py
new file mode 100755
index 00000000..d091a611
--- /dev/null
+++ b/tests/http2-server.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+
+import asyncio
+from functools import wraps
+from secrets import compare_digest
+import sys
+from urllib.parse import urlparse
+
+from quart import (
+    request,
+    make_response,
+    Quart,
+)
+
+app = Quart(__name__)
+
+@app.route('/')
+async def index():
+    return 'Hello world'
+
+@app.route('/slow')
+async def slow():
+    await asyncio.sleep(1)
+    return 'Hello world'
+
+@app.route('/no-content')
+async def no_content():
+    return await make_response('', 204)
+
+@app.route('/large')
+async def large():
+
+    async def generate_data():
+        # Send increasing letters just to aid debugging
+        letter = ord('A')
+        bytes_pending = 1024 * 24
+        while bytes_pending:
+            await asyncio.sleep(0.1)
+            bytes_pending -= 1024
+            string = chr(letter) * 1024
+            letter += 1
+            yield bytes(string, 'UTF-8')
+        yield b'\0'
+
+    return generate_data()
+
+@app.route('/echo_query')
+async def echo_query():
+    url = urlparse(request.url)
+    return url.query
+
+@app.route('/echo_post', methods=['POST'])
+async def echo_post():
+    data = await request.get_data()
+    return data
+
+@app.route('/auth')
+async def auth():
+    auth = request.authorization
+
+    if (
+        auth is not None and 
+        auth.type == "basic" and
+        auth.username == 'username' and
+        compare_digest(auth.password, 'password')
+    ):
+        return 'Authenticated'
+
+    response = await make_response('Authentication Required')
+    response.status_code = 401
+    response.headers['WWW-Authenticate'] = 'Basic'
+    return response
+
+has_been_misdirected = False
+
+@app.route('/misdirected_request')
+async def misdirected_request():
+    global has_been_misdirected
+
+    if not has_been_misdirected:
+        has_been_misdirected = True
+        response = await make_response('', 421)
+        return response
+
+    return 'Success!'
+
+if __name__ == '__main__':
+    # Always close so this is never left running by accident
+    loop = asyncio.get_event_loop()
+    loop.call_later(10, lambda: sys.exit(0))
+
+    app.run(use_reloader=False, loop=loop,
+        certfile='test-cert.pem',
+        keyfile='test-key.pem')
diff --git a/tests/http2-test.c b/tests/http2-test.c
new file mode 100644
index 00000000..4f4e69db
--- /dev/null
+++ b/tests/http2-test.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright 2021 Igalia S.L.
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "test-utils.h"
+#include "soup-connection.h"
+#include "soup-message-private.h"
+#include "soup-memory-input-stream.h"
+
+typedef struct {
+        SoupSession *session;
+        SoupMessage *msg;
+} Test;
+
+static void
+setup_session (Test *test, gconstpointer data)
+{
+        test->session = soup_test_session_new (NULL);
+}
+
+static void
+teardown_session (Test *test, gconstpointer data)
+{
+        if (test->msg) {
+                g_assert_cmpuint (soup_message_get_http_version (test->msg), ==, SOUP_HTTP_2_0);
+                g_object_unref (test->msg);
+        }
+
+        soup_test_session_abort_unref (test->session);
+}
+
+static void
+do_basic_async_test (Test *test, gconstpointer data)
+{
+        test->msg = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/";);
+        GError *error = NULL;
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Hello world");
+
+        g_bytes_unref (response);
+}
+
+static void
+do_basic_sync_test (Test *test, gconstpointer data)
+{
+        test->msg = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/";);
+        GError *error = NULL;
+
+        GBytes *response = soup_session_send_and_read (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Hello world");
+
+        g_bytes_unref (response);
+}
+
+static void
+do_no_content_async_test (Test *test, gconstpointer data)
+{
+        test->msg = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/no-content";);
+        GError *error = NULL;
+
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpuint (soup_message_get_status (test->msg), ==, 204);
+        g_assert_cmpuint (g_bytes_get_size (response), ==, 0);
+
+        g_bytes_unref (response);
+}
+
+static void
+do_large_async_test (Test *test, gconstpointer data)
+{
+        test->msg = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/large";);
+        GError *error = NULL;
+
+        /* This is both large and read in chunks */
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        // Size hardcoded to match http2-server.py's response
+        g_assert_cmpuint (g_bytes_get_size (response), ==, (1024 * 24) + 1);
+
+        g_bytes_unref (response);
+}
+
+static GBytes *
+read_stream_to_bytes_sync (GInputStream *stream)
+{
+        GOutputStream *out = g_memory_output_stream_new_resizable ();
+        GError *error = NULL;
+
+        gssize read = g_output_stream_splice (out, stream,  G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | 
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                              NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpint (read, >, 0);
+
+        GBytes *bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out));
+        g_object_unref (out);
+        return bytes;
+}
+
+static void
+on_send_complete (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+        SoupSession *sess = SOUP_SESSION (source);
+        GError *error = NULL;
+        GInputStream *stream;
+        GBytes **bytes_out = user_data;
+
+        stream = soup_session_send_finish (sess, res, &error);
+
+        g_assert_no_error (error);
+        g_assert_nonnull (stream);
+
+        *bytes_out = read_stream_to_bytes_sync (stream);
+        g_object_unref (stream);
+}
+
+static void
+do_multi_message_async_test (Test *test, gconstpointer data)
+{
+        GMainContext *async_context = g_main_context_ref_thread_default ();
+
+        SoupMessage *msg1 = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/echo_query?body%201";);
+        soup_message_set_http_version (msg1, SOUP_HTTP_2_0);
+
+        SoupMessage *msg2 = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/echo_query?body%202";);
+        soup_message_set_http_version (msg2, SOUP_HTTP_2_0);
+
+        GBytes *response1 = NULL;
+        GBytes *response2 = NULL;
+        soup_session_send_async (test->session, msg1, G_PRIORITY_DEFAULT, NULL, on_send_complete, 
&response1);
+        soup_session_send_async (test->session, msg2, G_PRIORITY_DEFAULT, NULL, on_send_complete, 
&response2);
+
+        while (!response1 || !response2) {
+                g_main_context_iteration (async_context, TRUE);
+        }
+
+        g_assert_cmpuint (soup_message_get_http_version (msg1), ==, SOUP_HTTP_2_0);
+        g_assert_cmpuint (soup_message_get_http_version (msg2), ==, SOUP_HTTP_2_0);
+
+        g_assert_cmpstr (g_bytes_get_data (response1, NULL), ==, "body%201");
+        g_assert_cmpstr (g_bytes_get_data (response2, NULL), ==, "body%202");
+
+        g_bytes_unref (response1);
+        g_bytes_unref (response2);
+        g_object_unref (msg1);
+        g_object_unref (msg2);
+        g_main_context_unref (async_context);
+}
+
+static void
+do_post_sync_test (Test *test, gconstpointer data)
+{
+        GBytes *bytes = g_bytes_new_static ("body 1", sizeof ("body 1"));
+        test->msg = soup_message_new (SOUP_METHOD_POST, "https://127.0.0.1:5000/echo_post";);
+        soup_message_set_request_body_from_bytes (test->msg, "text/plain", bytes);
+
+        GError *error = NULL;
+        GInputStream *response = soup_session_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_nonnull (response);
+
+        GBytes *response_bytes = read_stream_to_bytes_sync (response);
+        g_assert_cmpstr (g_bytes_get_data (response_bytes, NULL), ==, "body 1");
+
+        g_bytes_unref (response_bytes);
+        g_object_unref (response);
+        g_bytes_unref (bytes);
+
+}
+
+static void
+do_post_async_test (Test *test, gconstpointer data)
+{
+        GMainContext *async_context = g_main_context_ref_thread_default ();
+
+        GBytes *bytes = g_bytes_new_static ("body 1", sizeof ("body 1"));
+        test->msg = soup_message_new (SOUP_METHOD_POST, "https://127.0.0.1:5000/echo_post";);
+        soup_message_set_request_body_from_bytes (test->msg, "text/plain", bytes);
+
+        GBytes *response = NULL;
+        soup_session_send_async (test->session, test->msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, 
&response);
+
+        while (!response) {
+                g_main_context_iteration (async_context, TRUE);
+        }
+
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "body 1");
+
+        g_bytes_unref (response);
+        g_bytes_unref (bytes);
+        g_main_context_unref (async_context);
+}
+
+static void
+do_post_blocked_async_test (Test *test, gconstpointer data)
+{
+        GMainContext *async_context = g_main_context_ref_thread_default ();
+
+        GInputStream *in_stream = soup_memory_input_stream_new (NULL);
+        GBytes *bytes = g_bytes_new_static ("Part 1 -", 8);
+        soup_memory_input_stream_add_bytes (SOUP_MEMORY_INPUT_STREAM (in_stream), bytes);
+        g_bytes_unref (bytes);
+
+        test->msg = soup_message_new (SOUP_METHOD_POST, "https://127.0.0.1:5000/echo_post";);
+        soup_message_set_request_body (test->msg, "text/plain", in_stream, 8 + 8);
+
+        GBytes *response = NULL;
+        soup_session_send_async (test->session, test->msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, 
&response);
+
+        int iteration_count = 20;
+        while (!response) {
+                // Let it iterate for a bit waiting on blocked data
+                if (iteration_count-- == 0) {
+                        bytes = g_bytes_new_static (" Part 2", 8);
+                        soup_memory_input_stream_add_bytes (SOUP_MEMORY_INPUT_STREAM (in_stream), bytes);
+                        g_bytes_unref (bytes);
+                        soup_memory_input_stream_complete (SOUP_MEMORY_INPUT_STREAM (in_stream));
+                }
+                g_main_context_iteration (async_context, TRUE);
+        }
+
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Part 1 - Part 2");
+
+        g_bytes_unref (response);
+        g_object_unref (in_stream);
+        g_main_context_unref (async_context);
+}
+
+static void
+do_post_file_async_test (Test *test, gconstpointer data)
+{
+        GMainContext *async_context = g_main_context_ref_thread_default ();
+
+        GFile *in_file = g_file_new_for_path (g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL));
+        GFileInputStream *in_stream = g_file_read (in_file, NULL, NULL);
+        g_assert_nonnull (in_stream);
+
+        test->msg = soup_message_new (SOUP_METHOD_POST, "https://127.0.0.1:5000/echo_post";);
+        soup_message_set_request_body (test->msg, "application/x-x509-ca-cert", G_INPUT_STREAM (in_stream), 
-1);
+
+        GBytes *response = NULL;
+        soup_session_send_async (test->session, test->msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, 
&response);
+
+        while (!response)
+                g_main_context_iteration (async_context, TRUE);
+
+        g_assert_true (g_str_has_prefix (g_bytes_get_data (response, NULL), "-----BEGIN CERTIFICATE-----"));
+
+        g_bytes_unref (response);
+        g_object_unref (in_stream);
+        g_object_unref (in_file);
+        g_main_context_unref (async_context);
+}
+
+static gboolean
+on_delayed_auth (SoupAuth *auth)
+{
+        g_test_message ("Authenticating");
+        soup_auth_authenticate (auth, "username", "password");
+        return G_SOURCE_REMOVE;
+}
+
+static gboolean
+on_authenticate (SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data)
+{
+        g_test_message ("Authenticate request");
+        /* Force it to pause the message by delaying auth */
+        g_timeout_add (500, (GSourceFunc)on_delayed_auth, auth);
+        return TRUE;
+}
+
+static void
+do_paused_async_test (Test *test, gconstpointer data)
+{
+
+        test->msg = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/auth";);
+        g_signal_connect (test->msg, "authenticate", G_CALLBACK (on_authenticate), NULL);
+
+        GError *error = NULL;
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Authenticated");
+
+        g_bytes_unref (response);
+}
+
+static SoupConnection *last_connection;
+
+static void
+on_send_ready (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+        SoupSession *sess = SOUP_SESSION (source);
+        SoupMessage *msg = soup_session_get_async_result_message (sess, res);
+        guint *complete_count = user_data;
+        SoupConnection *conn;
+        GError *error = NULL;
+        GInputStream *stream;
+
+        stream = soup_session_send_finish (sess, res, &error);
+
+        g_assert_no_error (error);
+        g_assert_nonnull (stream);
+
+        GBytes *result = read_stream_to_bytes_sync (stream);
+        g_object_unref (stream);
+        g_assert_nonnull (result);
+        g_assert_cmpstr (g_bytes_get_data (result, NULL), ==, "Hello world");
+        g_bytes_unref (result);
+
+        g_assert_nonnull (msg);
+        g_assert_cmpuint (soup_message_get_http_version (msg), ==, SOUP_HTTP_2_0);
+        conn = soup_message_get_connection (msg);
+
+        if (last_connection)
+                g_assert (last_connection == conn);
+        else
+                last_connection = conn;
+        
+        g_test_message ("Conn (%u) = %p", *complete_count, conn);
+
+        *complete_count += 1;
+}
+
+static void
+do_connections_test (Test *test, gconstpointer data)
+{
+        GMainContext *async_context = g_main_context_ref_thread_default ();
+        guint complete_count = 0;
+
+#define N_TESTS 100
+
+        for (uint i = 0; i < N_TESTS; ++i) {
+                SoupMessage *msg = soup_message_new ("GET", "https://127.0.0.1:5000/slow";);
+                soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_ready, 
&complete_count);
+                g_object_unref (msg);
+        }
+
+        while (complete_count != N_TESTS) {
+                g_main_context_iteration (async_context, TRUE);
+        }
+
+        // After no messages reference the connection we should still be able to re-use the same connection
+        SoupMessage *msg = soup_message_new ("GET", "https://127.0.0.1:5000/slow";);
+        soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_ready, 
&complete_count);
+        g_object_unref (msg);
+
+        g_main_context_unref (async_context);
+}
+
+static void
+do_misdirected_request_test (Test *test, gconstpointer data)
+{
+        test->msg = soup_message_new (SOUP_METHOD_GET, "https://127.0.0.1:5000/misdirected_request";);
+        GError *error = NULL;
+
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Success!");
+
+        g_bytes_unref (response);
+}
+
+static void
+log_printer (SoupLogger *logger,
+             SoupLoggerLogLevel level,
+             char direction,
+             const char *data,
+             gpointer user_data)
+{
+        gboolean *has_logged_body = user_data;
+
+        // We are testing that the request body is logged
+        // which is backend specific for now
+        if (direction == '>' && g_strcmp0 (data, "Test") == 0)
+                *has_logged_body = TRUE;
+}
+
+static void
+do_logging_test (Test *test, gconstpointer data)
+{
+        gboolean has_logged_body = FALSE;
+
+        SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
+        soup_logger_set_printer (logger, log_printer, &has_logged_body, NULL);
+        soup_session_add_feature (test->session, SOUP_SESSION_FEATURE (logger));
+
+        GBytes *bytes = g_bytes_new_static ("Test", sizeof ("Test"));
+        test->msg = soup_message_new (SOUP_METHOD_POST, "https://127.0.0.1:5000/echo_post";);
+        soup_message_set_request_body_from_bytes (test->msg, "text/plain", bytes);
+        GError *error = NULL;
+
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Test");
+        g_assert_true (has_logged_body);
+
+        g_bytes_unref (response);
+}
+
+static void
+do_metrics_test (Test *test, gconstpointer data)
+{
+        GBytes *bytes = g_bytes_new_static ("Test", sizeof ("Test"));
+        test->msg = soup_message_new (SOUP_METHOD_POST, "https://127.0.0.1:5000/echo_post";);
+        soup_message_set_request_body_from_bytes (test->msg, "text/plain", bytes);
+        soup_message_add_flags (test->msg, SOUP_MESSAGE_COLLECT_METRICS);
+
+        GError *error = NULL;
+        GBytes *response = soup_test_session_async_send (test->session, test->msg, NULL, &error);
+
+        g_assert_no_error (error);
+        g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Test");
+
+        SoupMessageMetrics *metrics = soup_message_get_metrics (test->msg);
+        g_assert_nonnull (metrics);
+
+        g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), >, 0);
+        g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, g_bytes_get_size 
(bytes));
+        g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), >, 
soup_message_metrics_get_request_body_size (metrics));
+
+        g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), >, 0);
+        g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), ==, g_bytes_get_size 
(response));
+        g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), >, 
soup_message_metrics_get_response_body_size (metrics));
+
+        g_bytes_unref (response);
+        g_bytes_unref (bytes);     
+}
+
+
+int
+main (int argc, char **argv)
+{
+       int ret;
+
+       test_init (argc, argv, NULL);
+
+        if (!quart_init ()) {
+                test_cleanup ();
+                return 1;
+        }
+
+        g_test_add ("/http2/basic/async", Test, NULL,
+                    setup_session,
+                    do_basic_async_test,
+                    teardown_session);
+        g_test_add ("/http2/basic/sync", Test, NULL,
+                    setup_session,
+                    do_basic_sync_test,
+                    teardown_session);
+        g_test_add ("/http2/no_content/async", Test, NULL,
+                    setup_session,
+                    do_no_content_async_test,
+                    teardown_session);
+        g_test_add ("/http2/large/async", Test, NULL,
+                    setup_session,
+                    do_large_async_test,
+                    teardown_session);
+        g_test_add ("/http2/multiplexing/async", Test, NULL,
+                    setup_session,
+                    do_multi_message_async_test,
+                    teardown_session);
+        g_test_add ("/http2/post/async", Test, NULL,
+                    setup_session,
+                    do_post_async_test,
+                    teardown_session);
+        g_test_add ("/http2/post/sync", Test, NULL,
+                    setup_session,
+                    do_post_sync_test,
+                    teardown_session);
+        g_test_add ("/http2/post/blocked/async", Test, NULL,
+                    setup_session,
+                    do_post_blocked_async_test,
+                    teardown_session);
+        g_test_add ("/http2/post/file/async", Test, NULL,
+                    setup_session,
+                    do_post_file_async_test,
+                    teardown_session);
+        g_test_add ("/http2/paused/async", Test, NULL,
+                    setup_session,
+                    do_paused_async_test,
+                    teardown_session);
+        g_test_add ("/http2/connections", Test, NULL,
+                    setup_session,
+                    do_connections_test,
+                    teardown_session);
+        g_test_add ("/http2/misdirected_request", Test, NULL,
+                    setup_session,
+                    do_misdirected_request_test,
+                    teardown_session);
+        g_test_add ("/http2/logging", Test, NULL,
+                    setup_session,
+                    do_logging_test,
+                    teardown_session);
+        g_test_add ("/http2/metrics", Test, NULL,
+                    setup_session,
+                    do_metrics_test,
+                    teardown_session);
+
+
+       ret = g_test_run ();
+
+        test_cleanup ();
+
+       return ret;
+}
diff --git a/tests/memory-stream-test.c b/tests/memory-stream-test.c
new file mode 100644
index 00000000..b49e7eed
--- /dev/null
+++ b/tests/memory-stream-test.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021 Igalia S.L.
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "test-utils.h"
+#include "soup-memory-input-stream.h"
+
+static void
+do_large_data_test (void)
+{
+#define CHUNK_SIZE (gsize)1024 * 1024 * 512 // 512 MiB
+#define TEST_SIZE CHUNK_SIZE * 20 // 10 GiB
+
+        GInputStream *stream = soup_memory_input_stream_new (NULL);
+        SoupMemoryInputStream *mem_stream = SOUP_MEMORY_INPUT_STREAM (stream);
+        gsize data_needed = TEST_SIZE;
+        char *memory_chunk = g_new (char, CHUNK_SIZE); 
+        char *trash_buffer = g_new (char, CHUNK_SIZE);
+
+        // We can add unlimited data and as long as its read the data will
+        // be freed, so this should work fine even though its reading GB of data
+
+        while (data_needed > 0) {
+                // Copy chunk
+                GBytes *bytes = g_bytes_new (memory_chunk, CHUNK_SIZE);
+                soup_memory_input_stream_add_bytes (mem_stream, bytes);
+                g_bytes_unref (bytes);
+
+                // This should free the copy
+                gssize read = g_input_stream_read (stream, trash_buffer, CHUNK_SIZE, NULL, NULL);
+                g_assert_cmpint (read, ==, CHUNK_SIZE);
+
+                data_needed -= CHUNK_SIZE;
+                
+        }
+
+        g_free (trash_buffer);
+        g_free (memory_chunk);
+        g_object_unref (stream);
+}
+
+static void
+do_multiple_chunk_test (void)
+{
+        GInputStream *stream = soup_memory_input_stream_new (NULL);
+        SoupMemoryInputStream *mem_stream = SOUP_MEMORY_INPUT_STREAM (stream);
+        const char * const chunks[] = {
+                "1234", "5678", "9012", "hell", "owor", "ld..",
+        };
+
+        for (guint i = 0; i < G_N_ELEMENTS (chunks); ++i) {
+                GBytes *bytes = g_bytes_new_static (chunks[i], 4);
+                g_assert (g_bytes_get_size (bytes) == 4);
+                soup_memory_input_stream_add_bytes (mem_stream, bytes);
+                g_bytes_unref (bytes);
+        }
+
+        // Do partial reads of chunks to ensure it always comes out as expected
+        for (guint i = 0; i < G_N_ELEMENTS (chunks); ++i) {
+                char buffer[5] = { 0 };
+                gssize read = g_input_stream_read (stream, buffer, 2, NULL, NULL);
+                g_assert_cmpint (read, ==, 2);
+                read = g_input_stream_read (stream, buffer + 2, 2, NULL, NULL);
+                g_assert_cmpint (read, ==, 2);
+                g_assert_cmpstr (buffer, ==, chunks[i]);
+        }
+
+        g_object_unref (stream);
+}
+
+int
+main (int argc, char **argv)
+{
+       int ret;
+
+       test_init (argc, argv, NULL);
+
+       g_test_add_func ("/memory_stream/large_data", do_large_data_test);
+        g_test_add_func ("/memory_stream/multiple_chunks", do_multiple_chunk_test);
+
+       ret = g_test_run ();
+
+        test_cleanup ();
+
+       return ret;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 0892cccf..38ff77c6 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -47,6 +47,7 @@ tests = [
   ['hsts-db', true, []],
   ['logger', true, []],
   ['misc', true, []],
+  ['memory-stream', true, []],
   ['multipart', true, []],
   ['no-ssl', true, []],
   ['ntlm', true, []],
@@ -66,6 +67,12 @@ tests = [
   ['websocket', true, [libz_dep]]
 ]
 
+if quart_found
+  tests += [
+    ['http2', true, []],
+  ]
+endif
+
 if brotlidec_dep.found()
   tests += [
     ['brotli-decompressor', true, []],
diff --git a/tests/test-utils.c b/tests/test-utils.c
index d8b9c68d..d4b299ba 100644
--- a/tests/test-utils.c
+++ b/tests/test-utils.c
@@ -112,6 +112,8 @@ test_cleanup (void)
                apache_cleanup ();
 #endif
 
+        quart_cleanup ();
+
        if (logger)
                g_object_unref (logger);
        if (index_buffer)
@@ -256,6 +258,75 @@ apache_cleanup (void)
 
 #endif /* HAVE_APACHE */
 
+static GSubprocess *quart_proc;
+
+gboolean
+quart_init (void)
+{
+        if (quart_proc)
+                return TRUE;
+
+        GSubprocessLauncher *launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); // | 
G_SUBPROCESS_FLAGS_STDERR_SILENCE
+        g_subprocess_launcher_set_cwd (launcher, g_test_get_dir (G_TEST_DIST));
+
+        GError *error = NULL;
+        char *script = g_test_build_filename (G_TEST_DIST, "http2-server.py", NULL);
+        quart_proc = g_subprocess_launcher_spawn (launcher, &error, script, NULL);
+        g_free (script);
+        g_object_unref (launcher);
+
+        if (error) {
+                g_test_message ("Failed to start quart server: %s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        GDataInputStream *in_stream = g_data_input_stream_new (g_subprocess_get_stdout_pipe (quart_proc));
+
+        // We don't own the stream, don't break the pipe
+        g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (in_stream), FALSE);
+
+        // Read stdout until the server says it is running
+        while (TRUE) {
+                char *line = g_data_input_stream_read_line_utf8 (in_stream, NULL, NULL, &error);
+
+                if (error) {
+                        g_test_message ("Failed to start quart server: %s", error->message);
+                        g_error_free (error);
+                        g_object_unref (in_stream);
+                        return FALSE;
+                } else if (line == NULL) {
+                        g_test_message ("Failed to start quart server (not installed?)");
+                        g_object_unref (in_stream);
+                        return FALSE;
+                }
+
+                if (g_str_has_prefix (line, " * Running")) {
+                        g_test_message ("Started quart server: %s", line + 3);
+                        g_free (line);
+                        g_object_unref (in_stream);
+                        return TRUE;
+                }
+                g_free (line);
+        }
+}
+
+void
+quart_cleanup (void)
+{
+        if (quart_proc) {
+                GError *error = NULL;
+                g_subprocess_force_exit (quart_proc);
+                g_subprocess_wait (quart_proc, NULL, &error);
+                if (error) {
+                        g_test_message ("Failed to stop quart server: %s", error->message);
+                        g_error_free (error);
+                }
+        }
+
+        g_clear_object (&quart_proc);
+}
+
 SoupSession *
 soup_test_session_new (const char *propname, ...)
 {
diff --git a/tests/test-utils.h b/tests/test-utils.h
index 1b4cfa75..6a9a37c6 100644
--- a/tests/test-utils.h
+++ b/tests/test-utils.h
@@ -47,6 +47,9 @@ void apache_cleanup (void);
        } G_STMT_END
 #endif
 
+gboolean quart_init    (void);
+void     quart_cleanup (void);
+
 gboolean have_curl (void);
 
 typedef enum {


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