[libsoup/wip/http2-b: 2/3] Add SoupHTTPChannel to abstract HTTP I/O



commit d12416cdc816d579a3eb3b6a57199848b7c0ec48
Author: Dan Winship <danw gnome org>
Date:   Sun Dec 8 14:48:52 2013 +0100

    Add SoupHTTPChannel to abstract HTTP I/O
    
    Add an abstract SoupHTTPChannel class, and a SoupHTTP1Channel
    implementation for HTTP/1.x. This incorporates all of the code that
    was formerly in soup-message-client-io.c and soup-message-server-io.c,
    along with various other bits.

 libsoup/Makefile.am               |    6 +-
 libsoup/soup-body-output-stream.c |   20 +-
 libsoup/soup-body-output-stream.h |    8 +-
 libsoup/soup-connection.c         |   10 +-
 libsoup/soup-http-channel.c       |  313 ++++++++++++++++++++
 libsoup/soup-http-channel.h       |   85 ++++++
 libsoup/soup-http1-channel.c      |  587 +++++++++++++++++++++++++++++++++++++
 libsoup/soup-http1-channel.h      |   35 +++
 libsoup/soup-message-client-io.c  |  166 -----------
 libsoup/soup-message-io.c         |  243 +++------------
 libsoup/soup-message-private.h    |   36 +--
 libsoup/soup-message-server-io.c  |  281 ------------------
 libsoup/soup-server.c             |   13 +-
 po/POTFILES.in                    |    3 +-
 14 files changed, 1119 insertions(+), 687 deletions(-)
---
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 19dff18..da96e63 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -139,16 +139,18 @@ libsoup_2_4_la_SOURCES =          \
        soup-filter-input-stream.h      \
        soup-form.c                     \
        soup-headers.c                  \
+       soup-http-channel.c             \
+       soup-http-channel.h             \
+       soup-http1-channel.c            \
+       soup-http1-channel.h            \
        soup-logger.c                   \
        soup-message.c                  \
        soup-message-body.c             \
-       soup-message-client-io.c        \
        soup-message-headers.c          \
        soup-message-io.c               \
        soup-message-private.h          \
        soup-message-queue.h            \
        soup-message-queue.c            \
-       soup-message-server-io.c        \
        soup-method.c                   \
        soup-misc.c                     \
        soup-misc-private.h             \
diff --git a/libsoup/soup-body-output-stream.c b/libsoup/soup-body-output-stream.c
index 03353ef..a2c7e61 100644
--- a/libsoup/soup-body-output-stream.c
+++ b/libsoup/soup-body-output-stream.c
@@ -61,6 +61,11 @@ soup_body_output_stream_constructed (GObject *object)
        SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (object);
 
        bostream->priv->base_stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM 
(bostream));
+
+       if (bostream->priv->encoding == SOUP_ENCODING_NONE ||
+           (bostream->priv->encoding == SOUP_ENCODING_CONTENT_LENGTH &&
+            bostream->priv->write_length == 0))
+               bostream->priv->eof = TRUE;
 }
 
 static void
@@ -116,10 +121,8 @@ soup_body_output_stream_write_raw (SoupBodyOutputStream  *bostream,
         */
        if (bostream->priv->write_length) {
                my_count = MIN (count, bostream->priv->write_length - bostream->priv->written);
-               if (my_count == 0) {
-                       bostream->priv->eof = TRUE;
+               if (my_count == 0)
                        return count;
-               }
        } else
                my_count = count;
 
@@ -127,8 +130,11 @@ soup_body_output_stream_write_raw (SoupBodyOutputStream  *bostream,
                                          buffer, my_count,
                                          blocking, cancellable, error);
 
-       if (nwrote > 0 && bostream->priv->write_length)
+       if (nwrote > 0 && bostream->priv->write_length) {
                bostream->priv->written += nwrote;
+               if (bostream->priv->written == bostream->priv->write_length)
+                       bostream->priv->eof = TRUE;
+       }
 
        if (nwrote == my_count && my_count != count)
                nwrote = count;
@@ -344,3 +350,9 @@ soup_body_output_stream_new (GOutputStream *base_stream,
                             "content-length", content_length,
                             NULL);
 }
+
+gboolean
+soup_body_output_stream_get_eof (SoupBodyOutputStream *bostream)
+{
+       return bostream->priv->eof;
+}
diff --git a/libsoup/soup-body-output-stream.h b/libsoup/soup-body-output-stream.h
index 8bd8970..2617f99 100644
--- a/libsoup/soup-body-output-stream.h
+++ b/libsoup/soup-body-output-stream.h
@@ -38,9 +38,11 @@ typedef struct {
 
 GType soup_body_output_stream_get_type (void);
 
-GOutputStream *soup_body_output_stream_new (GOutputStream *base_stream,
-                                           SoupEncoding   encoding,
-                                           goffset        content_length);
+GOutputStream *soup_body_output_stream_new     (GOutputStream        *base_stream,
+                                               SoupEncoding          encoding,
+                                               goffset               content_length);
+
+gboolean       soup_body_output_stream_get_eof (SoupBodyOutputStream *bostream);
 
 G_END_DECLS
 
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 436b8cb..652d9ce 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -702,6 +702,7 @@ soup_connection_send_request (SoupConnection          *conn,
                              gpointer                 user_data)
 {
        SoupConnectionPrivate *priv;
+       GMainContext *async_context;
 
        g_return_if_fail (SOUP_IS_CONNECTION (conn));
        g_return_if_fail (item != NULL);
@@ -714,5 +715,12 @@ soup_connection_send_request (SoupConnection          *conn,
        else
                priv->reusable = FALSE;
 
-       soup_message_send_request (item, completion_cb, user_data);
+       if (!SOUP_IS_SESSION_SYNC (item->session)) {
+               async_context = soup_session_get_async_context (item->session);
+               if (!async_context)
+                       async_context = g_main_context_default ();
+       } else
+               async_context = NULL;
+
+       soup_message_io_client (item, async_context, completion_cb, user_data);
 }
diff --git a/libsoup/soup-http-channel.c b/libsoup/soup-http-channel.c
new file mode 100644
index 0000000..867bb0b
--- /dev/null
+++ b/libsoup/soup-http-channel.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-http-channel.c
+ *
+ * Copyright 2014 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-http-channel.h"
+#include "soup.h"
+#include "soup-misc-private.h"
+
+/**
+ * SECTION:soup-http-channel
+ * @short_description: Channel for a single HTTP request/response
+ *
+ * #SoupHTTPChannel is an abstract type representing a communication
+ * channel for handling a single HTTP request and response.
+ */
+
+G_DEFINE_ABSTRACT_TYPE (SoupHTTPChannel, soup_http_channel, G_TYPE_OBJECT)
+
+enum {
+       PROP_0,
+
+       PROP_MESSAGE,
+       PROP_MODE
+};
+
+typedef struct {
+       SoupMessage *msg;
+       SoupHTTPChannelMode mode;
+
+} SoupHTTPChannelPrivate;
+#define SOUP_HTTP_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_HTTP_CHANNEL, 
SoupHTTPChannelPrivate))
+
+static void
+soup_http_channel_init (SoupHTTPChannel *channel)
+{
+}
+
+static void
+soup_http_channel_set_property (GObject *object, guint prop_id,
+                               const GValue *value, GParamSpec *pspec)
+{
+       SoupHTTPChannelPrivate *priv = SOUP_HTTP_CHANNEL_GET_PRIVATE (object);
+
+       switch (prop_id) {
+       case PROP_MESSAGE:
+               priv->msg = g_value_dup_object (value);
+               break;
+       case PROP_MODE:
+               priv->mode = g_value_get_int (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+soup_http_channel_get_property (GObject *object, guint prop_id,
+                               GValue *value, GParamSpec *pspec)
+{
+       SoupHTTPChannelPrivate *priv = SOUP_HTTP_CHANNEL_GET_PRIVATE (object);
+
+       switch (prop_id) {
+       case PROP_MESSAGE:
+               g_value_set_object (value, priv->msg);
+               break;
+       case PROP_MODE:
+               g_value_set_int (value, priv->mode);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+soup_http_channel_dispose (GObject *object)
+{
+       SoupHTTPChannelPrivate *priv = SOUP_HTTP_CHANNEL_GET_PRIVATE (object);
+
+       g_clear_object (&priv->msg);
+
+       G_OBJECT_CLASS (soup_http_channel_parent_class)->dispose (object);
+}
+
+static void
+soup_http_channel_class_init (SoupHTTPChannelClass *channel_class)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (channel_class);
+
+       g_type_class_add_private (channel_class, sizeof (SoupHTTPChannelPrivate));
+
+       object_class->set_property = soup_http_channel_set_property;
+       object_class->get_property = soup_http_channel_get_property;
+       object_class->dispose      = soup_http_channel_dispose;
+
+       g_object_class_install_property (
+                object_class, PROP_MESSAGE,
+                g_param_spec_object (SOUP_HTTP_CHANNEL_MESSAGE,
+                                     "SoupMessage",
+                                     "The channel's SoupMessage",
+                                     SOUP_TYPE_MESSAGE,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_STATIC_STRINGS |
+                                     G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (
+                object_class, PROP_MODE,
+                /* We don't parse private headers for enum types, so use int */
+                g_param_spec_int (SOUP_HTTP_CHANNEL_MODE,
+                                  "Mode",
+                                  "The channel's SoupHTTPChannelMode",
+                                  -1, G_MAXINT, -1,
+                                  G_PARAM_READWRITE |
+                                  G_PARAM_STATIC_STRINGS |
+                                  G_PARAM_CONSTRUCT_ONLY));
+}
+
+gboolean
+soup_http_channel_read_headers (SoupHTTPChannel      *channel,
+                               gboolean              blocking,
+                               GCancellable         *cancellable,
+                               GError              **error)
+{
+       if (SOUP_HTTP_CHANNEL_GET_PRIVATE (channel)->mode == SOUP_HTTP_CHANNEL_CLIENT) {
+               return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->
+                       read_response_headers (channel, blocking, cancellable, error);
+       } else {
+               return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->
+                       read_request_headers (channel, blocking, cancellable, error);
+       }
+}
+
+static void
+handle_partial_get (SoupMessage *msg)
+{
+       SoupRange *ranges;
+       int nranges;
+       SoupBuffer *full_response;
+       guint status;
+
+       /* Make sure the message is set up right for us to return a
+        * partial response; it has to be a GET, the status must be
+        * 200 OK (and in particular, NOT already 206 Partial
+        * Content), and the SoupServer must have already filled in
+        * the response body
+        */
+       if (msg->method != SOUP_METHOD_GET ||
+           msg->status_code != SOUP_STATUS_OK ||
+           soup_message_headers_get_encoding (msg->response_headers) !=
+           SOUP_ENCODING_CONTENT_LENGTH ||
+           msg->response_body->length == 0 ||
+           !soup_message_body_get_accumulate (msg->response_body))
+               return;
+
+       /* Oh, and there has to have been a valid Range header on the
+        * request, of course.
+        */
+       status = soup_message_headers_get_ranges_internal (msg->request_headers,
+                                                          msg->response_body->length,
+                                                          TRUE,
+                                                          &ranges, &nranges);
+       if (status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
+               soup_message_set_status (msg, status);
+               soup_message_body_truncate (msg->response_body);
+               return;
+       } else if (status != SOUP_STATUS_PARTIAL_CONTENT)
+               return;
+
+       full_response = soup_message_body_flatten (msg->response_body);
+       if (!full_response) {
+               soup_message_headers_free_ranges (msg->request_headers, ranges);
+               return;
+       }
+
+       soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
+       soup_message_body_truncate (msg->response_body);
+
+       if (nranges == 1) {
+               SoupBuffer *range_buf;
+
+               /* Single range, so just set Content-Range and fix the body. */
+
+               soup_message_headers_set_content_range (msg->response_headers,
+                                                       ranges[0].start,
+                                                       ranges[0].end,
+                                                       full_response->length);
+               range_buf = soup_buffer_new_subbuffer (full_response,
+                                                      ranges[0].start,
+                                                      ranges[0].end - ranges[0].start + 1);
+               soup_message_body_append_buffer (msg->response_body, range_buf);
+               soup_buffer_free (range_buf);
+       } else {
+               SoupMultipart *multipart;
+               SoupMessageHeaders *part_headers;
+               SoupBuffer *part_body;
+               const char *content_type;
+               int i;
+
+               /* Multiple ranges, so build a multipart/byteranges response
+                * to replace msg->response_body with.
+                */
+
+               multipart = soup_multipart_new ("multipart/byteranges");
+               content_type = soup_message_headers_get_one (msg->response_headers,
+                                                            "Content-Type");
+               for (i = 0; i < nranges; i++) {
+                       part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+                       if (content_type) {
+                               soup_message_headers_append (part_headers,
+                                                            "Content-Type",
+                                                            content_type);
+                       }
+                       soup_message_headers_set_content_range (part_headers,
+                                                               ranges[i].start,
+                                                               ranges[i].end,
+                                                               full_response->length);
+                       part_body = soup_buffer_new_subbuffer (full_response,
+                                                              ranges[i].start,
+                                                              ranges[i].end - ranges[i].start + 1);
+                       soup_multipart_append_part (multipart, part_headers,
+                                                   part_body);
+                       soup_message_headers_free (part_headers);
+                       soup_buffer_free (part_body);
+               }
+
+               soup_multipart_to_message (multipart, msg->response_headers,
+                                          msg->response_body);
+               soup_multipart_free (multipart);
+       }
+
+       soup_buffer_free (full_response);
+       soup_message_headers_free_ranges (msg->request_headers, ranges);
+}
+
+gboolean
+soup_http_channel_write_headers (SoupHTTPChannel      *channel,
+                                gboolean              blocking,
+                                GCancellable         *cancellable,
+                                GError              **error)
+{
+       SoupHTTPChannelPrivate *priv = SOUP_HTTP_CHANNEL_GET_PRIVATE (channel);
+
+       if (SOUP_HTTP_CHANNEL_GET_PRIVATE (channel)->mode == SOUP_HTTP_CHANNEL_CLIENT) {
+               SoupEncoding encoding;
+
+               /* Fix up unspecified encoding */
+               encoding = soup_message_headers_get_encoding (priv->msg->request_headers);
+               if ((encoding == SOUP_ENCODING_CONTENT_LENGTH ||
+                    encoding == SOUP_ENCODING_NONE) &&
+                   (priv->msg->request_body->length > 0 ||
+                    soup_message_headers_get_one (priv->msg->request_headers, "Content-Type")) &&
+                   !soup_message_headers_get_content_length (priv->msg->request_headers)) {
+                       soup_message_headers_set_content_length (priv->msg->request_headers,
+                                                                priv->msg->request_body->length);
+               }
+
+               return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->
+                       write_request_headers (channel, blocking, cancellable, error);
+       } else {
+               handle_partial_get (priv->msg);
+
+               /* Fix up unspecified encoding */
+               if (soup_message_headers_get_encoding (priv->msg->response_headers) == 
SOUP_ENCODING_CONTENT_LENGTH &&
+                   !soup_message_headers_get_content_length (priv->msg->response_headers)) {
+                       soup_message_headers_set_content_length (priv->msg->response_headers,
+                                                                priv->msg->response_body->length);
+               }
+
+               return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->
+                       write_response_headers (channel, blocking, cancellable, error);
+       }
+}
+
+GInputStream *
+soup_http_channel_get_body_input_stream (SoupHTTPChannel *channel)
+{
+       return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->get_body_input_stream (channel);
+}
+
+GOutputStream *
+soup_http_channel_get_body_output_stream (SoupHTTPChannel *channel)
+{
+       return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->get_body_output_stream (channel);
+}
+
+GSource *
+soup_http_channel_create_oneshot_source (SoupHTTPChannel  *channel,
+                                        GIOCondition      cond,
+                                        GCancellable     *cancellable)
+{
+       g_return_val_if_fail (cond == G_IO_IN || cond == G_IO_OUT, NULL);
+
+       return SOUP_HTTP_CHANNEL_GET_CLASS (channel)->create_oneshot_source (channel, cond, cancellable);
+}
+
+SoupMessage *
+soup_http_channel_get_message (SoupHTTPChannel *channel)
+{
+       return SOUP_HTTP_CHANNEL_GET_PRIVATE (channel)->msg;
+}
+
+SoupHTTPChannelMode
+soup_http_channel_get_mode (SoupHTTPChannel *channel)
+{
+       return SOUP_HTTP_CHANNEL_GET_PRIVATE (channel)->mode;
+}
diff --git a/libsoup/soup-http-channel.h b/libsoup/soup-http-channel.h
new file mode 100644
index 0000000..4926a90
--- /dev/null
+++ b/libsoup/soup-http-channel.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2014 Red Hat, Inc.
+ */
+
+#ifndef SOUP_HTTP_CHANNEL_H
+#define SOUP_HTTP_CHANNEL_H 1
+
+#include "soup-types.h"
+
+#define SOUP_TYPE_HTTP_CHANNEL            (soup_http_channel_get_type ())
+#define SOUP_HTTP_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_HTTP_CHANNEL, 
SoupHTTPChannel))
+#define SOUP_HTTP_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_HTTP_CHANNEL, 
SoupHTTPChannelClass))
+#define SOUP_IS_HTTP_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_HTTP_CHANNEL))
+#define SOUP_IS_HTTP_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_HTTP_CHANNEL))
+#define SOUP_HTTP_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_HTTP_CHANNEL, 
SoupHTTPChannelClass))
+
+#define SOUP_HTTP_CHANNEL_MESSAGE "message"
+#define SOUP_HTTP_CHANNEL_MODE    "mode"
+
+typedef struct {
+       GObject parent;
+
+} SoupHTTPChannel;
+
+typedef struct {
+       GObjectClass parent_class;
+
+       gboolean        (*read_request_headers)   (SoupHTTPChannel  *channel,
+                                                  gboolean          blocking,
+                                                  GCancellable     *cancellable,
+                                                  GError          **error);
+
+       gboolean        (*read_response_headers)  (SoupHTTPChannel  *channel,
+                                                  gboolean          blocking,
+                                                  GCancellable     *cancellable,
+                                                  GError          **error);
+
+       gboolean        (*write_request_headers)  (SoupHTTPChannel  *channel,
+                                                  gboolean          blocking,
+                                                  GCancellable     *cancellable,
+                                                  GError          **error);
+
+       gboolean        (*write_response_headers) (SoupHTTPChannel  *channel,
+                                                  gboolean          blocking,
+                                                  GCancellable     *cancellable,
+                                                  GError          **error);
+
+       GInputStream *  (*get_body_input_stream)  (SoupHTTPChannel  *channel);
+       GOutputStream * (*get_body_output_stream) (SoupHTTPChannel  *channel);
+
+       GSource *       (*create_oneshot_source)  (SoupHTTPChannel  *channel,
+                                                  GIOCondition      cond,
+                                                  GCancellable     *cancellable);
+
+} SoupHTTPChannelClass;
+
+typedef enum {
+       SOUP_HTTP_CHANNEL_CLIENT,
+       SOUP_HTTP_CHANNEL_SERVER
+} SoupHTTPChannelMode;
+
+GType soup_http_channel_get_type (void);
+
+gboolean            soup_http_channel_read_headers           (SoupHTTPChannel  *channel,
+                                                             gboolean          blocking,
+                                                             GCancellable     *cancellable,
+                                                             GError          **error);
+
+gboolean            soup_http_channel_write_headers          (SoupHTTPChannel  *channel,
+                                                             gboolean          blocking,
+                                                             GCancellable     *cancellable,
+                                                             GError          **error);
+
+GInputStream *      soup_http_channel_get_body_input_stream  (SoupHTTPChannel  *channel);
+GOutputStream *     soup_http_channel_get_body_output_stream (SoupHTTPChannel  *channel);
+
+GSource *           soup_http_channel_create_oneshot_source  (SoupHTTPChannel  *channel,
+                                                             GIOCondition      cond,
+                                                             GCancellable     *cancellable);
+
+SoupMessage *       soup_http_channel_get_message            (SoupHTTPChannel  *channel);
+SoupHTTPChannelMode soup_http_channel_get_mode               (SoupHTTPChannel  *channel);
+
+#endif /* SOUP_HTTP_CHANNEL_H */
diff --git a/libsoup/soup-http1-channel.c b/libsoup/soup-http1-channel.c
new file mode 100644
index 0000000..332d49f
--- /dev/null
+++ b/libsoup/soup-http1-channel.c
@@ -0,0 +1,587 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-http1-channel.c
+ *
+ * Copyright 2014 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "soup-http1-channel.h"
+#include "soup.h"
+#include "soup-body-input-stream.h"
+#include "soup-body-output-stream.h"
+#include "soup-filter-input-stream.h"
+#include "soup-socket-private.h"
+
+G_DEFINE_TYPE (SoupHTTP1Channel, soup_http1_channel, SOUP_TYPE_HTTP_CHANNEL)
+
+typedef struct {
+       SoupSocket *server_sock;
+
+       SoupFilterInputStream *istream;
+       GPollableInputStream *poll_istream;
+       GOutputStream *ostream;
+       GPollableOutputStream *poll_ostream;
+
+       GString *input_headers, *output_headers;
+       gboolean headers_read;
+       gsize headers_nwritten;
+
+       SoupEncoding input_encoding, output_encoding;
+       goffset input_length, output_length;
+
+} SoupHTTP1ChannelPrivate;
+#define SOUP_HTTP1_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_HTTP1_CHANNEL, 
SoupHTTP1ChannelPrivate))
+
+static void
+soup_http1_channel_init (SoupHTTP1Channel *channel)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+
+       priv->input_headers = g_string_new (NULL);
+       priv->output_headers = g_string_new (NULL);
+}
+
+#define READ_BUFFER_SIZE 8192
+
+static gboolean
+read_headers (SoupHTTPChannel *channel,
+             gboolean blocking,
+             GCancellable *cancellable,
+             GError **error)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       gssize nread, old_len;
+       gboolean got_lf;
+
+       if (priv->headers_read) {
+               /* restart */
+               g_string_truncate (priv->input_headers, 0);
+               priv->headers_read = FALSE;
+       }
+
+       while (1) {
+               old_len = priv->input_headers->len;
+               g_string_set_size (priv->input_headers, old_len + READ_BUFFER_SIZE);
+               nread = soup_filter_input_stream_read_line (priv->istream,
+                                                           priv->input_headers->str + old_len,
+                                                           READ_BUFFER_SIZE,
+                                                           blocking,
+                                                           &got_lf,
+                                                           cancellable, error);
+               priv->input_headers->len = old_len + MAX (nread, 0);
+
+               if (nread < 0)
+                       return FALSE;
+               else if (nread == 0) {
+                       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+                                            _("Connection terminated unexpectedly"));
+                       return FALSE;
+               }
+
+               if (got_lf) {
+                       if (nread == 1 && old_len >= 2 &&
+                           !strncmp (priv->input_headers->str +
+                                     priv->input_headers->len - 2,
+                                     "\n\n", 2))
+                               break;
+                       else if (nread == 2 && old_len >= 3 &&
+                                !strncmp (priv->input_headers->str +
+                                          priv->input_headers->len - 3,
+                                          "\n\r\n", 3))
+                               break;
+               }
+       }
+
+       /* We need to "rewind" priv->input_headers back one line.
+        * That SHOULD be two characters (CR LF), but if the
+        * web server was stupid, it might only be one.
+        */
+       if (priv->input_headers->len < 3 ||
+           priv->input_headers->str[priv->input_headers->len - 2] == '\n')
+               priv->input_headers->len--;
+       else
+               priv->input_headers->len -= 2;
+       priv->input_headers->str[priv->input_headers->len] = '\0';
+
+       priv->headers_read = TRUE;
+       return TRUE;
+}
+
+static gboolean
+soup_http1_channel_read_request_headers (SoupHTTPChannel      *channel,
+                                        gboolean              blocking,
+                                        GCancellable         *cancellable,
+                                        GError              **error)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       SoupMessage *msg = soup_http_channel_get_message (channel);
+       guint status;
+       char *req_method, *req_path, *uri_string;
+       const char *req_host;
+       SoupHTTPVersion version;
+       SoupURI *uri;
+
+       if (!read_headers (channel, blocking, cancellable, error))
+               return FALSE;
+
+       status = soup_headers_parse_request (priv->input_headers->str,
+                                            priv->input_headers->len,
+                                            msg->request_headers,
+                                            &req_method, &req_path, &version);
+
+       if (status != SOUP_STATUS_OK) {
+       failed:
+               g_set_error_literal (error, SOUP_HTTP_ERROR, status,
+                                    _("Could not parse HTTP request"));
+               return FALSE;
+       }
+
+       g_object_set (msg,
+                     SOUP_MESSAGE_METHOD, req_method,
+                     SOUP_MESSAGE_HTTP_VERSION, version,
+                     NULL);
+       g_free (req_method);
+
+       /* Handle request body encoding */
+       priv->input_encoding = soup_message_headers_get_encoding (msg->request_headers);
+       if (priv->input_encoding == SOUP_ENCODING_UNRECOGNIZED) {
+               g_free (req_path);
+               if (soup_message_headers_get_list (msg->request_headers, "Transfer-Encoding"))
+                       status = SOUP_STATUS_NOT_IMPLEMENTED;
+               else
+                       status = SOUP_STATUS_BAD_REQUEST;
+               goto failed;
+       }
+       if (priv->input_encoding == SOUP_ENCODING_CONTENT_LENGTH)
+               priv->input_length = soup_message_headers_get_content_length (msg->request_headers);
+       else
+               priv->input_length = -1;
+
+       /* Generate correct context for request */
+       req_host = soup_message_headers_get_one (msg->request_headers, "Host");
+       if (req_host && strchr (req_host, '/')) {
+               g_free (req_path);
+               status = SOUP_STATUS_BAD_REQUEST;
+               goto failed;
+       }
+
+       if (!strcmp (req_path, "*") && req_host) {
+               /* Eg, "OPTIONS * HTTP/1.1" */
+               uri_string = g_strdup_printf ("%s://%s",
+                                             soup_socket_is_ssl (priv->server_sock) ? "https" : "http",
+                                             req_host);
+               uri = soup_uri_new (uri_string);
+               if (uri)
+                       soup_uri_set_path (uri, "*");
+               g_free (uri_string);
+       } else if (*req_path != '/') {
+               /* Must be an absolute URI */
+               uri = soup_uri_new (req_path);
+       } else if (req_host) {
+               uri_string = g_strdup_printf ("%s://%s%s",
+                                             soup_socket_is_ssl (priv->server_sock) ? "https" : "http",
+                                             req_host, req_path);
+               uri = soup_uri_new (uri_string);
+               g_free (uri_string);
+       } else if (version == SOUP_HTTP_1_0) {
+               /* No Host header, no AbsoluteUri */
+               SoupAddress *addr = soup_socket_get_local_address (priv->server_sock);
+
+               uri = soup_uri_new (NULL);
+               soup_uri_set_scheme (uri, soup_socket_is_ssl (priv->server_sock) ? "https" : "http");
+               soup_uri_set_host (uri, soup_address_get_physical (addr));
+               soup_uri_set_port (uri, soup_address_get_port (addr));
+               soup_uri_set_path (uri, req_path);
+       } else
+               uri = NULL;
+
+       g_free (req_path);
+
+       if (!uri || !uri->host) {
+               if (uri)
+                       soup_uri_free (uri);
+               status = SOUP_STATUS_BAD_REQUEST;
+               goto failed;
+       }
+
+       g_object_set (msg,
+                     SOUP_MESSAGE_URI, uri,
+                     NULL);
+       soup_uri_free (uri);
+
+       return TRUE;
+}
+
+static gboolean
+soup_http1_channel_read_response_headers (SoupHTTPChannel      *channel,
+                                         gboolean              blocking,
+                                         GCancellable         *cancellable,
+                                         GError              **error)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       SoupMessage *msg = soup_http_channel_get_message (channel);
+       SoupHTTPVersion version;
+       guint status_code;
+       char *reason_phrase;
+       gboolean ok;
+
+       if (!read_headers (channel, blocking, cancellable, error))
+               return FALSE;
+
+       ok = soup_headers_parse_response (priv->input_headers->str,
+                                         priv->input_headers->len,
+                                         msg->response_headers,
+                                         &version, &status_code, &reason_phrase);
+
+       if (!ok) {
+               g_set_error_literal (error, SOUP_HTTP_ERROR,
+                                    SOUP_STATUS_MALFORMED,
+                                    _("Could not parse HTTP response"));
+               return FALSE;
+       }
+
+       g_object_set (msg,
+                     SOUP_MESSAGE_STATUS_CODE, status_code,
+                     SOUP_MESSAGE_REASON_PHRASE, reason_phrase,
+                     SOUP_MESSAGE_HTTP_VERSION, MIN (version, soup_message_get_http_version (msg)),
+                     NULL);
+
+       if (msg->method == SOUP_METHOD_HEAD ||
+           status_code == SOUP_STATUS_NO_CONTENT ||
+           status_code == SOUP_STATUS_NOT_MODIFIED ||
+           SOUP_STATUS_IS_INFORMATIONAL (status_code) ||
+           (msg->method == SOUP_METHOD_CONNECT &&
+            SOUP_STATUS_IS_SUCCESSFUL (status_code)))
+               priv->input_encoding = SOUP_ENCODING_NONE;
+       else {
+               priv->input_encoding = soup_message_headers_get_encoding (msg->response_headers);
+
+               if (priv->input_encoding == SOUP_ENCODING_UNRECOGNIZED) {
+                       g_set_error_literal (error, SOUP_HTTP_ERROR,
+                                            SOUP_STATUS_NOT_IMPLEMENTED,
+                                            _("Unrecognized HTTP encoding"));
+                       return FALSE;
+               }
+       }
+
+       if (priv->input_encoding == SOUP_ENCODING_CONTENT_LENGTH) {
+               const char *conn;
+
+               priv->input_length = soup_message_headers_get_content_length (msg->response_headers);
+
+               /* Some servers suck and send incorrect Content-Length
+                * values, so if the message isn't keepalive anyway, allow
+                * EOF termination.
+                */
+               conn = soup_message_headers_get_one (msg->response_headers, "Connection");
+               if (version == SOUP_HTTP_1_0 &&
+                   (!conn || !soup_header_contains (conn, "Keep-Alive")))
+                       priv->input_encoding = SOUP_ENCODING_EOF;
+               else if (version == SOUP_HTTP_1_1 && conn &&
+                        soup_header_contains (conn, "close"))
+                       priv->input_encoding = SOUP_ENCODING_EOF;
+       } else
+               priv->input_length = -1;
+
+       return TRUE;
+}
+
+static gboolean
+write_headers (SoupHTTPChannel  *channel,
+              gboolean          blocking,
+              GCancellable     *cancellable,
+              GError          **error)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       gssize nwrote;
+
+       while (priv->headers_nwritten < priv->output_headers->len) {
+               nwrote = g_pollable_stream_write (priv->ostream,
+                                                 priv->output_headers->str + priv->headers_nwritten,
+                                                 priv->output_headers->len - priv->headers_nwritten,
+                                                 blocking, cancellable, error);
+               if (nwrote == -1)
+                       return FALSE;
+               priv->headers_nwritten += nwrote;
+       }
+       return TRUE;
+}
+
+static void
+finish_build_headers (SoupHTTPChannel    *channel,
+                     SoupMessageHeaders *headers)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       SoupMessageHeadersIter iter;
+       const char *name, *value;
+
+       if (priv->output_encoding == SOUP_ENCODING_CONTENT_LENGTH)
+               priv->output_length = soup_message_headers_get_content_length (headers);
+
+       soup_message_headers_iter_init (&iter, headers);
+       while (soup_message_headers_iter_next (&iter, &name, &value))
+               g_string_append_printf (priv->output_headers, "%s: %s\r\n", name, value);
+       g_string_append (priv->output_headers, "\r\n");
+}
+
+static void
+build_request_headers (SoupHTTPChannel *channel)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       SoupMessage *msg = soup_http_channel_get_message (channel);
+       SoupURI *request_uri = soup_message_get_uri (msg);
+       char *uri_host;
+       char *uri_string;
+
+       g_string_truncate (priv->output_headers, 0);
+       priv->headers_nwritten = 0;
+
+       if (strchr (request_uri->host, ':'))
+               uri_host = g_strdup_printf ("[%.*s]", (int) strcspn (request_uri->host, "%"), 
request_uri->host);
+       else if (g_hostname_is_non_ascii (request_uri->host))
+               uri_host = g_hostname_to_ascii (request_uri->host);
+       else
+               uri_host = request_uri->host;
+
+       if (msg->method == SOUP_METHOD_CONNECT) {
+               /* CONNECT URI is hostname:port for tunnel destination */
+               uri_string = g_strdup_printf ("%s:%d", uri_host, request_uri->port);
+       } else {
+               /* Proxy expects full URI to destination. Otherwise
+                * just the path.
+                */
+               if (soup_connection_is_via_proxy (soup_message_get_connection (msg))) {
+                       uri_string = soup_uri_to_string (request_uri, FALSE);
+                       if (request_uri->fragment) {
+                               /* Strip fragment */
+                               char *fragment = strchr (uri_string, '#');
+                               if (fragment)
+                                       *fragment = '\0';
+                       }
+               } else
+                       uri_string = soup_uri_to_string (request_uri, TRUE);
+       }
+
+       g_string_append_printf (priv->output_headers, "%s %s HTTP/1.%d\r\n",
+                               msg->method, uri_string,
+                               (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) ? 0 : 1);
+
+       if (!soup_message_headers_get_one (msg->request_headers, "Host")) {
+               if (soup_uri_uses_default_port (request_uri)) {
+                       g_string_append_printf (priv->output_headers, "Host: %s\r\n",
+                                               uri_host);
+               } else {
+                       g_string_append_printf (priv->output_headers, "Host: %s:%d\r\n",
+                                               uri_host, request_uri->port);
+               }
+       }
+       g_free (uri_string);
+       if (uri_host != request_uri->host)
+               g_free (uri_host);
+
+       priv->output_encoding = soup_message_headers_get_encoding (msg->request_headers);
+       finish_build_headers (channel, msg->request_headers);
+}
+
+static void
+build_response_headers (SoupHTTPChannel *channel)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       SoupMessage *msg = soup_http_channel_get_message (channel);
+       SoupEncoding claimed_encoding;
+
+       g_string_truncate (priv->output_headers, 0);
+       priv->headers_nwritten = 0;
+
+       g_string_append_printf (priv->output_headers, "HTTP/1.%c %d %s\r\n",
+                               (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) ? '0' : '1',
+                               msg->status_code, msg->reason_phrase);
+
+       claimed_encoding = soup_message_headers_get_encoding (msg->response_headers);
+       if ((msg->method == SOUP_METHOD_HEAD ||
+            msg->status_code == SOUP_STATUS_NO_CONTENT ||
+            msg->status_code == SOUP_STATUS_NOT_MODIFIED ||
+            SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) ||
+           (msg->method == SOUP_METHOD_CONNECT &&
+            SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)))
+               priv->output_encoding = SOUP_ENCODING_NONE;
+       else
+               priv->output_encoding = claimed_encoding;
+
+       finish_build_headers (channel, msg->response_headers);
+}
+
+static gboolean
+soup_http1_channel_write_request_headers (SoupHTTPChannel  *channel,
+                                         gboolean          blocking,
+                                         GCancellable     *cancellable,
+                                         GError          **error)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+
+       if (priv->headers_nwritten == priv->output_headers->len)
+               build_request_headers (channel);
+       return write_headers (channel, blocking, cancellable, error);
+}
+
+static gboolean
+soup_http1_channel_write_response_headers (SoupHTTPChannel  *channel,
+                                          gboolean          blocking,
+                                          GCancellable     *cancellable,
+                                          GError          **error)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+
+       if (priv->headers_nwritten == priv->output_headers->len)
+               build_response_headers (channel);
+       return write_headers (channel, blocking, cancellable, error);
+}
+
+static GInputStream *
+soup_http1_channel_get_body_input_stream (SoupHTTPChannel *channel)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       GInputStream *istream;
+
+       g_return_val_if_fail (priv->headers_read, NULL);
+
+       istream = soup_body_input_stream_new (G_INPUT_STREAM (priv->istream),
+                                             priv->input_encoding,
+                                             priv->input_length);
+       g_object_unref (priv->poll_istream);
+       priv->poll_istream = g_object_ref (istream);
+
+       return istream;
+}
+
+static GOutputStream *
+soup_http1_channel_get_body_output_stream (SoupHTTPChannel *channel)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+       GOutputStream *ostream;
+
+       g_return_val_if_fail (priv->headers_nwritten != 0, NULL);
+
+       ostream = soup_body_output_stream_new (priv->ostream,
+                                              priv->output_encoding,
+                                              priv->output_length);
+       g_object_unref (priv->poll_ostream);
+       priv->poll_ostream = g_object_ref (ostream);
+
+       return ostream;
+}
+
+static GSource *
+soup_http1_channel_create_oneshot_source (SoupHTTPChannel  *channel,
+                                         GIOCondition      cond,
+                                         GCancellable     *cancellable)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+
+       if (cond == G_IO_IN)
+               return g_pollable_input_stream_create_source (priv->poll_istream, cancellable);
+       else if (cond == G_IO_OUT)
+               return g_pollable_output_stream_create_source (priv->poll_ostream, cancellable);
+       else
+               g_assert_not_reached ();
+}
+
+static void
+soup_http1_channel_dispose (GObject *object)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (object);
+
+       g_clear_object (&priv->server_sock);
+       g_clear_object (&priv->istream);
+       g_clear_object (&priv->poll_istream);
+       g_clear_object (&priv->ostream);
+       g_clear_object (&priv->poll_ostream);
+
+       G_OBJECT_CLASS (soup_http1_channel_parent_class)->dispose (object);
+}
+
+static void
+soup_http1_channel_finalize (GObject *object)
+{
+       SoupHTTP1ChannelPrivate *priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (object);
+
+       g_string_free (priv->input_headers, TRUE);
+       g_string_free (priv->output_headers, TRUE);
+
+       G_OBJECT_CLASS (soup_http1_channel_parent_class)->finalize (object);
+}
+
+static void
+soup_http1_channel_class_init (SoupHTTP1ChannelClass *http1_channel_class)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (http1_channel_class);
+       SoupHTTPChannelClass *channel_class = SOUP_HTTP_CHANNEL_CLASS (http1_channel_class);
+
+       g_type_class_add_private (http1_channel_class, sizeof (SoupHTTP1ChannelPrivate));
+
+       object_class->dispose = soup_http1_channel_dispose;
+       object_class->finalize = soup_http1_channel_finalize;
+
+       channel_class->read_request_headers = soup_http1_channel_read_request_headers;
+       channel_class->read_response_headers = soup_http1_channel_read_response_headers;
+       channel_class->write_request_headers = soup_http1_channel_write_request_headers;
+       channel_class->write_response_headers = soup_http1_channel_write_response_headers;
+       channel_class->get_body_input_stream = soup_http1_channel_get_body_input_stream;
+       channel_class->get_body_output_stream = soup_http1_channel_get_body_output_stream;
+       channel_class->create_oneshot_source = soup_http1_channel_create_oneshot_source;
+}
+
+SoupHTTPChannel *
+soup_http1_channel_new_client (SoupMessage *msg)
+{
+       SoupHTTPChannel *channel;
+       SoupHTTP1ChannelPrivate *priv;
+       GIOStream *iostream;
+
+       channel = g_object_new (SOUP_TYPE_HTTP1_CHANNEL,
+                               SOUP_HTTP_CHANNEL_MESSAGE, msg,
+                               SOUP_HTTP_CHANNEL_MODE, SOUP_HTTP_CHANNEL_CLIENT,
+                               NULL);
+       priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+
+       iostream = soup_socket_get_iostream (soup_connection_get_socket (soup_message_get_connection (msg)));
+       priv->istream = g_object_ref (g_io_stream_get_input_stream (iostream));
+       priv->poll_istream = g_object_ref (priv->istream);
+       priv->ostream = g_object_ref (g_io_stream_get_output_stream (iostream));
+       priv->poll_ostream = g_object_ref (priv->ostream);
+
+       return channel;
+}
+
+SoupHTTPChannel *
+soup_http1_channel_new_server (SoupMessage *msg,
+                              SoupSocket *sock)
+{
+       SoupHTTPChannel *channel;
+       SoupHTTP1ChannelPrivate *priv;
+       GIOStream *iostream;
+
+       channel = g_object_new (SOUP_TYPE_HTTP1_CHANNEL,
+                               SOUP_HTTP_CHANNEL_MESSAGE, msg,
+                               SOUP_HTTP_CHANNEL_MODE, SOUP_HTTP_CHANNEL_SERVER,
+                               NULL);
+       priv = SOUP_HTTP1_CHANNEL_GET_PRIVATE (channel);
+
+       priv->server_sock = g_object_ref (sock);
+
+       iostream = soup_socket_get_iostream (sock);
+       priv->istream = g_object_ref (g_io_stream_get_input_stream (iostream));
+       priv->poll_istream = g_object_ref (priv->istream);
+       priv->ostream = g_object_ref (g_io_stream_get_output_stream (iostream));
+       priv->poll_ostream = g_object_ref (priv->ostream);
+
+       return channel;
+}
diff --git a/libsoup/soup-http1-channel.h b/libsoup/soup-http1-channel.h
new file mode 100644
index 0000000..d26ad54
--- /dev/null
+++ b/libsoup/soup-http1-channel.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2014 Red Hat, Inc.
+ */
+
+#ifndef SOUP_HTTP1_CHANNEL_H
+#define SOUP_HTTP1_CHANNEL_H 1
+
+#include "soup-http-channel.h"
+#include "soup-connection.h"
+
+#define SOUP_TYPE_HTTP1_CHANNEL            (soup_http1_channel_get_type ())
+#define SOUP_HTTP1_CHANNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_HTTP1_CHANNEL, 
SoupHTTP1Channel))
+#define SOUP_HTTP1_CHANNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_HTTP1_CHANNEL, 
SoupHTTP1ChannelClass))
+#define SOUP_IS_HTTP1_CHANNEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_HTTP1_CHANNEL))
+#define SOUP_IS_HTTP1_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_HTTP1_CHANNEL))
+#define SOUP_HTTP1_CHANNEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_HTTP1_CHANNEL, 
SoupHTTP1ChannelClass))
+
+typedef struct {
+       SoupHTTPChannel parent;
+
+} SoupHTTP1Channel;
+
+typedef struct {
+       SoupHTTPChannelClass parent_class;
+
+} SoupHTTP1ChannelClass;
+
+GType soup_http1_channel_get_type (void);
+
+SoupHTTPChannel *soup_http1_channel_new_client (SoupMessage *msg);
+SoupHTTPChannel *soup_http1_channel_new_server (SoupMessage *msg,
+                                               SoupSocket *socket);
+
+#endif /* SOUP_HTTP1_CHANNEL_H */
diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c
index abe909f..834bd0f 100644
--- a/libsoup/soup-message-io.c
+++ b/libsoup/soup-message-io.c
@@ -19,6 +19,7 @@
 #include "soup-content-processor.h"
 #include "soup-content-sniffer-stream.h"
 #include "soup-filter-input-stream.h"
+#include "soup-http1-channel.h"
 #include "soup-message-private.h"
 #include "soup-message-queue.h"
 #include "soup-misc-private.h"
@@ -54,18 +55,14 @@ typedef struct {
        SoupMessageIOMode     mode;
        GCancellable         *cancellable;
 
-       GIOStream              *iostream;
-       SoupFilterInputStream  *istream;
-       GInputStream           *body_istream;
-       GOutputStream          *ostream;
-       GOutputStream          *body_ostream;
-       GMainContext           *async_context;
+       SoupHTTPChannel      *channel;
+       GInputStream         *body_istream;
+       GOutputStream        *body_ostream;
+       GMainContext         *async_context;
 
        SoupMessageIOState    read_state;
        SoupEncoding          read_encoding;
-       GByteArray           *read_header_buf;
        SoupMessageBody      *read_body;
-       goffset               read_length;
 
        SoupMessageIOState    write_state;
        SoupEncoding          write_encoding;
@@ -73,7 +70,6 @@ typedef struct {
        SoupMessageBody      *write_body;
        SoupBuffer           *write_chunk;
        goffset               write_body_offset;
-       goffset               write_length;
        goffset               written;
 
        GSource *io_source;
@@ -83,9 +79,6 @@ typedef struct {
        GCancellable *async_close_wait;
        GError       *async_close_error;
 
-       SoupMessageGetHeadersFn   get_headers_cb;
-       SoupMessageParseHeadersFn parse_headers_cb;
-       gpointer                  header_data;
        SoupMessageCompletionFn   completion_cb;
        gpointer                  completion_data;
 } SoupMessageIOData;
@@ -107,22 +100,14 @@ soup_message_io_cleanup (SoupMessage *msg)
                return;
        priv->io_data = NULL;
 
-       if (io->iostream)
-               g_object_unref (io->iostream);
-       if (io->body_istream)
-               g_object_unref (io->body_istream);
-       if (io->body_ostream)
-               g_object_unref (io->body_ostream);
-       if (io->async_context)
-               g_main_context_unref (io->async_context);
-       if (io->item)
-               soup_message_queue_item_unref (io->item);
-
-       g_byte_array_free (io->read_header_buf, TRUE);
+       g_clear_object (&io->channel);
+       g_clear_object (&io->body_istream);
+       g_clear_object (&io->body_ostream);
+       g_clear_pointer (&io->async_context, g_main_context_unref);
+       g_clear_pointer (&io->item, soup_message_queue_item_unref);
 
        g_string_free (io->write_buf, TRUE);
-       if (io->write_chunk)
-               soup_buffer_free (io->write_chunk);
+       g_clear_pointer (&io->write_chunk, soup_buffer_free);
 
        g_slice_free (SoupMessageIOData, io);
 }
@@ -174,62 +159,6 @@ soup_message_io_finished (SoupMessage *msg)
        g_object_unref (msg);
 }
 
-static gboolean
-read_headers (SoupMessage *msg, gboolean blocking,
-             GCancellable *cancellable, GError **error)
-{
-       SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
-       SoupMessageIOData *io = priv->io_data;
-       gssize nread, old_len;
-       gboolean got_lf;
-
-       while (1) {
-               old_len = io->read_header_buf->len;
-               g_byte_array_set_size (io->read_header_buf, old_len + RESPONSE_BLOCK_SIZE);
-               nread = soup_filter_input_stream_read_line (io->istream,
-                                                           io->read_header_buf->data + old_len,
-                                                           RESPONSE_BLOCK_SIZE,
-                                                           blocking,
-                                                           &got_lf,
-                                                           cancellable, error);
-               io->read_header_buf->len = old_len + MAX (nread, 0);
-               if (nread == 0) {
-                       soup_message_set_status (msg, SOUP_STATUS_MALFORMED);
-                       g_set_error_literal (error, G_IO_ERROR,
-                                            G_IO_ERROR_PARTIAL_INPUT,
-                                            _("Connection terminated unexpectedly"));
-               }
-               if (nread <= 0)
-                       return FALSE;
-
-               if (got_lf) {
-                       if (nread == 1 && old_len >= 2 &&
-                           !strncmp ((char *)io->read_header_buf->data +
-                                     io->read_header_buf->len - 2,
-                                     "\n\n", 2))
-                               break;
-                       else if (nread == 2 && old_len >= 3 &&
-                                !strncmp ((char *)io->read_header_buf->data +
-                                          io->read_header_buf->len - 3,
-                                          "\n\r\n", 3))
-                               break;
-               }
-       }
-
-       /* We need to "rewind" io->read_header_buf back one line.
-        * That SHOULD be two characters (CR LF), but if the
-        * web server was stupid, it might only be one.
-        */
-       if (io->read_header_buf->len < 3 ||
-           io->read_header_buf->data[io->read_header_buf->len - 2] == '\n')
-               io->read_header_buf->len--;
-       else
-               io->read_header_buf->len -= 2;
-       io->read_header_buf->data[io->read_header_buf->len] = '\0';
-
-       return TRUE;
-}
-
 static gint
 processing_stage_cmp (gconstpointer a,
                    gconstpointer b)
@@ -360,25 +289,11 @@ io_write (SoupMessage *msg, gboolean blocking,
 
        switch (io->write_state) {
        case SOUP_MESSAGE_IO_STATE_HEADERS:
-               if (!io->write_buf->len) {
-                       io->get_headers_cb (msg, io->write_buf,
-                                           &io->write_encoding,
-                                           io->header_data);
-               }
-
-               while (io->written < io->write_buf->len) {
-                       nwrote = g_pollable_stream_write (io->ostream,
-                                                         io->write_buf->str + io->written,
-                                                         io->write_buf->len - io->written,
-                                                         blocking,
-                                                         cancellable, error);
-                       if (nwrote == -1)
-                               return FALSE;
-                       io->written += nwrote;
-               }
+               if (!soup_http_channel_write_headers (io->channel, blocking,
+                                                     cancellable, error))
+                       return FALSE;
 
                io->written = 0;
-               g_string_truncate (io->write_buf, 0);
 
                if (io->mode == SOUP_MESSAGE_IO_SERVER &&
                    SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) {
@@ -402,13 +317,6 @@ io_write (SoupMessage *msg, gboolean blocking,
                        break;
                }
 
-               if (io->write_encoding == SOUP_ENCODING_CONTENT_LENGTH) {
-                       SoupMessageHeaders *hdrs =
-                               (io->mode == SOUP_MESSAGE_IO_CLIENT) ?
-                               msg->request_headers : msg->response_headers;
-                       io->write_length = soup_message_headers_get_content_length (hdrs);
-               }
-
                if (io->mode == SOUP_MESSAGE_IO_CLIENT &&
                    soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) 
{
                        /* Need to wait for the Continue response */
@@ -431,17 +339,13 @@ io_write (SoupMessage *msg, gboolean blocking,
 
 
        case SOUP_MESSAGE_IO_STATE_BODY_START:
-               io->body_ostream = soup_body_output_stream_new (io->ostream,
-                                                               io->write_encoding,
-                                                               io->write_length);
+               io->body_ostream = soup_http_channel_get_body_output_stream (io->channel);
                io->write_state = SOUP_MESSAGE_IO_STATE_BODY;
                break;
 
 
        case SOUP_MESSAGE_IO_STATE_BODY:
-               if (!io->write_length &&
-                   io->write_encoding != SOUP_ENCODING_EOF &&
-                   io->write_encoding != SOUP_ENCODING_CHUNKED) {
+               if (soup_body_output_stream_get_eof (SOUP_BODY_OUTPUT_STREAM (io->body_ostream))) {
                        io->write_state = SOUP_MESSAGE_IO_STATE_BODY_DONE;
                        break;
                }
@@ -470,9 +374,6 @@ io_write (SoupMessage *msg, gboolean blocking,
                chunk = soup_buffer_new_subbuffer (io->write_chunk,
                                                   io->written, nwrote);
                io->written += nwrote;
-               if (io->write_length)
-                       io->write_length -= nwrote;
-
                if (io->written == io->write_chunk->length)
                        io->write_state = SOUP_MESSAGE_IO_STATE_BODY_DATA;
 
@@ -556,20 +457,19 @@ io_read (SoupMessage *msg, gboolean blocking,
        guchar *stack_buf = NULL;
        gssize nread;
        SoupBuffer *buffer;
-       guint status;
+       GError *local = NULL;
 
        switch (io->read_state) {
        case SOUP_MESSAGE_IO_STATE_HEADERS:
-               if (!read_headers (msg, blocking, cancellable, error))
-                       return FALSE;
+               soup_http_channel_read_headers (io->channel, blocking,
+                                               cancellable, &local);
 
-               status = io->parse_headers_cb (msg, (char *)io->read_header_buf->data,
-                                              io->read_header_buf->len,
-                                              &io->read_encoding,
-                                              io->header_data, error);
-               g_byte_array_set_size (io->read_header_buf, 0);
+               if (local) {
+                       if (local->domain != SOUP_HTTP_ERROR) {
+                               g_propagate_error (error, local);
+                               return FALSE;
+                       }
 
-               if (status != SOUP_STATUS_OK) {
                        /* Either we couldn't parse the headers, or they
                         * indicated something that would mean we wouldn't
                         * be able to parse the body. (Eg, unknown
@@ -577,7 +477,8 @@ io_read (SoupMessage *msg, gboolean blocking,
                         * reading, and make sure the connection gets
                         * closed when we're done.
                         */
-                       soup_message_set_status (msg, status);
+                       soup_message_set_status (msg, local->code);
+                       g_clear_error (&local);
                        soup_message_headers_append (msg->request_headers,
                                                     "Connection", "close");
                        io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING;
@@ -625,33 +526,13 @@ io_read (SoupMessage *msg, gboolean blocking,
                                io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING;
                }
 
-               if (io->read_encoding == SOUP_ENCODING_CONTENT_LENGTH) {
-                       SoupMessageHeaders *hdrs =
-                               (io->mode == SOUP_MESSAGE_IO_CLIENT) ?
-                               msg->response_headers : msg->request_headers;
-                       io->read_length = soup_message_headers_get_content_length (hdrs);
-
-                       if (io->mode == SOUP_MESSAGE_IO_CLIENT &&
-                           !soup_message_is_keepalive (msg)) {
-                               /* Some servers suck and send
-                                * incorrect Content-Length values, so
-                                * allow EOF termination in this case
-                                * (iff the message is too short) too.
-                                */
-                               io->read_encoding = SOUP_ENCODING_EOF;
-                       }
-               } else
-                       io->read_length = -1;
-
                soup_message_got_headers (msg);
                break;
 
 
        case SOUP_MESSAGE_IO_STATE_BODY_START:
                if (!io->body_istream) {
-                       GInputStream *body_istream = soup_body_input_stream_new (G_INPUT_STREAM (io->istream),
-                                                                                io->read_encoding,
-                                                                                io->read_length);
+                       GInputStream *body_istream = soup_http_channel_get_body_input_stream (io->channel);
 
                        /* TODO: server-side messages do not have a io->item. This means
                         * that we cannot use content processors for them right now.
@@ -685,7 +566,7 @@ io_read (SoupMessage *msg, gboolean blocking,
 
        case SOUP_MESSAGE_IO_STATE_BODY:
                if (priv->chunk_allocator) {
-                       buffer = priv->chunk_allocator (msg, io->read_length, priv->chunk_allocator_data);
+                       buffer = priv->chunk_allocator (msg, 0 /* FIXME? */, priv->chunk_allocator_data);
                        if (!buffer) {
                                g_return_val_if_fail (!io->item || !io->item->new_api, FALSE);
                                soup_message_io_pause (msg);
@@ -839,29 +720,17 @@ soup_message_io_get_oneshot_source (SoupMessage *msg, GCancellable *cancellable,
        GSource *base_source, *source;
        SoupMessageSource *message_source;
 
-       if (!io) {
+       if (!io)
                base_source = g_timeout_source_new (0);
-       } else if (io->paused) {
+       else if (io->paused)
                base_source = NULL;
-       } else if (io->async_close_wait) {
+       else if (io->async_close_wait)
                base_source = g_cancellable_source_new (io->async_close_wait);
-       } else if (SOUP_MESSAGE_IO_STATE_POLLABLE (io->read_state)) {
-               GPollableInputStream *istream;
-
-               if (io->body_istream)
-                       istream = G_POLLABLE_INPUT_STREAM (io->body_istream);
-               else
-                       istream = G_POLLABLE_INPUT_STREAM (io->istream);
-               base_source = g_pollable_input_stream_create_source (istream, cancellable);
-       } else if (SOUP_MESSAGE_IO_STATE_POLLABLE (io->write_state)) {
-               GPollableOutputStream *ostream;
-
-               if (io->body_ostream)
-                       ostream = G_POLLABLE_OUTPUT_STREAM (io->body_ostream);
-               else
-                       ostream = G_POLLABLE_OUTPUT_STREAM (io->ostream);
-               base_source = g_pollable_output_stream_create_source (ostream, cancellable);
-       } else
+       else if (SOUP_MESSAGE_IO_STATE_POLLABLE (io->read_state))
+               base_source = soup_http_channel_create_oneshot_source (io->channel, G_IO_IN, cancellable);
+       else if (SOUP_MESSAGE_IO_STATE_POLLABLE (io->write_state))
+               base_source = soup_http_channel_create_oneshot_source (io->channel, G_IO_OUT, cancellable);
+       else
                base_source = g_timeout_source_new (0);
 
        source = g_source_new (&message_source_funcs,
@@ -891,7 +760,7 @@ request_is_restartable (SoupMessage *msg, GError *error)
 
        return (io->mode == SOUP_MESSAGE_IO_CLIENT &&
                io->read_state <= SOUP_MESSAGE_IO_STATE_HEADERS &&
-               io->read_header_buf->len == 0 &&
+               //FIXME io->read_header_buf->len == 0 &&
                soup_connection_get_ever_used (io->item->conn) &&
                !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) &&
                !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) &&
@@ -1106,11 +975,9 @@ soup_message_io_get_response_istream (SoupMessage  *msg,
 
 
 static SoupMessageIOData *
-new_iostate (SoupMessage *msg, GIOStream *iostream,
-            GMainContext *async_context, SoupMessageIOMode mode,
-            SoupMessageGetHeadersFn get_headers_cb,
-            SoupMessageParseHeadersFn parse_headers_cb,
-            gpointer header_data,
+new_iostate (SoupMessage *msg,
+            GMainContext *async_context,
+            SoupMessageIOMode mode,
             SoupMessageCompletionFn completion_cb,
             gpointer completion_data)
 {
@@ -1119,21 +986,13 @@ new_iostate (SoupMessage *msg, GIOStream *iostream,
 
        io = g_slice_new0 (SoupMessageIOData);
        io->mode = mode;
-       io->get_headers_cb   = get_headers_cb;
-       io->parse_headers_cb = parse_headers_cb;
-       io->header_data      = header_data;
        io->completion_cb    = completion_cb;
        io->completion_data  = completion_data;
 
-       io->iostream = g_object_ref (iostream);
-       io->istream = SOUP_FILTER_INPUT_STREAM (g_io_stream_get_input_stream (iostream));
-       io->ostream = g_io_stream_get_output_stream (iostream);
-
        if (async_context)
                io->async_context = g_main_context_ref (async_context);
 
-       io->read_header_buf = g_byte_array_new ();
-       io->write_buf       = g_string_new (NULL);
+       io->write_buf = g_string_new (NULL);
 
        io->read_state  = SOUP_MESSAGE_IO_STATE_NOT_STARTED;
        io->write_state = SOUP_MESSAGE_IO_STATE_NOT_STARTED;
@@ -1146,22 +1005,19 @@ new_iostate (SoupMessage *msg, GIOStream *iostream,
 
 void
 soup_message_io_client (SoupMessageQueueItem *item,
-                       GIOStream *iostream,
                        GMainContext *async_context,
-                       SoupMessageGetHeadersFn get_headers_cb,
-                       SoupMessageParseHeadersFn parse_headers_cb,
-                       gpointer header_data,
                        SoupMessageCompletionFn completion_cb,
                        gpointer completion_data)
 {
        SoupMessageIOData *io;
 
-       io = new_iostate (item->msg, iostream, async_context,
+       io = new_iostate (item->msg, async_context,
                          SOUP_MESSAGE_IO_CLIENT,
-                         get_headers_cb, parse_headers_cb, header_data,
                          completion_cb, completion_data);
 
+       io->channel = soup_http1_channel_new_client (item->msg);
        io->item = item;
+
        soup_message_queue_item_ref (item);
        io->cancellable = item->cancellable;
 
@@ -1180,20 +1036,19 @@ soup_message_io_client (SoupMessageQueueItem *item,
 
 void
 soup_message_io_server (SoupMessage *msg,
-                       GIOStream *iostream, GMainContext *async_context,
-                       SoupMessageGetHeadersFn get_headers_cb,
-                       SoupMessageParseHeadersFn parse_headers_cb,
-                       gpointer header_data,
+                       SoupSocket *sock,
+                       GMainContext *async_context,
                        SoupMessageCompletionFn completion_cb,
                        gpointer completion_data)
 {
        SoupMessageIOData *io;
 
-       io = new_iostate (msg, iostream, async_context,
+       io = new_iostate (msg, async_context,
                          SOUP_MESSAGE_IO_SERVER,
-                         get_headers_cb, parse_headers_cb, header_data,
                          completion_cb, completion_data);
 
+       io->channel = soup_http1_channel_new_server (msg, sock);
+
        io->read_body       = msg->request_body;
        io->write_body      = msg->response_body;
 
diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h
index 9cb4b19..6416f59 100644
--- a/libsoup/soup-message-private.h
+++ b/libsoup/soup-message-private.h
@@ -10,6 +10,7 @@
 #include "soup-auth.h"
 #include "soup-content-processor.h"
 #include "soup-content-sniffer.h"
+#include "soup-http-channel.h"
 #include "soup-session.h"
 
 typedef struct {
@@ -49,44 +50,17 @@ typedef struct {
 void             soup_message_cleanup_response (SoupMessage      *msg);
 
 
-typedef void     (*SoupMessageGetHeadersFn)  (SoupMessage      *msg,
-                                             GString          *headers,
-                                             SoupEncoding     *encoding,
-                                             gpointer          user_data);
-typedef guint    (*SoupMessageParseHeadersFn)(SoupMessage      *msg,
-                                             char             *headers,
-                                             guint             header_len,
-                                             SoupEncoding     *encoding,
-                                             gpointer          user_data,
-                                             GError          **error);
-typedef void     (*SoupMessageCompletionFn)  (SoupMessage      *msg,
-                                             gboolean          io_complete,
-                                             gpointer          user_data);
-
-
-void soup_message_send_request (SoupMessageQueueItem      *item,
-                               SoupMessageCompletionFn    completion_cb,
-                               gpointer                   user_data);
-void soup_message_read_request (SoupMessage               *msg,
-                               SoupSocket                *sock,
-                               gboolean                   use_thread_context,
-                               SoupMessageCompletionFn    completion_cb,
-                               gpointer                   user_data);
+typedef void     (*SoupMessageCompletionFn)   (SoupMessage          *msg,
+                                              gboolean              io_complete,
+                                              gpointer              user_data);
 
 void soup_message_io_client    (SoupMessageQueueItem      *item,
-                               GIOStream                 *iostream,
                                GMainContext              *async_context,
-                               SoupMessageGetHeadersFn    get_headers_cb,
-                               SoupMessageParseHeadersFn  parse_headers_cb,
-                               gpointer                   headers_data,
                                SoupMessageCompletionFn    completion_cb,
                                gpointer                   user_data);
 void soup_message_io_server    (SoupMessage               *msg,
-                               GIOStream                 *iostream,
+                               SoupSocket                *sock,
                                GMainContext              *async_context,
-                               SoupMessageGetHeadersFn    get_headers_cb,
-                               SoupMessageParseHeadersFn  parse_headers_cb,
-                               gpointer                   headers_data,
                                SoupMessageCompletionFn    completion_cb,
                                gpointer                   user_data);
 void soup_message_io_cleanup   (SoupMessage               *msg);
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c
index 30dc4f9..e1ceaba 100644
--- a/libsoup/soup-server.c
+++ b/libsoup/soup-server.c
@@ -1287,6 +1287,7 @@ start_request (SoupServer *server, SoupClientContext *client)
 {
        SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
        SoupMessage *msg;
+       GMainContext *async_context;
 
        soup_client_context_cleanup (client);
 
@@ -1309,9 +1310,15 @@ start_request (SoupServer *server, SoupClientContext *client)
 
        g_object_ref (client->sock);
 
-       soup_message_read_request (msg, client->sock,
-                                  priv->legacy_iface == NULL,
-                                  request_finished, client);
+       if (priv->legacy_iface == NULL)
+               async_context = g_main_context_ref_thread_default ();
+       else if (priv->async_context)
+               async_context = priv->async_context;
+       else
+               async_context = g_main_context_default ();
+
+       soup_message_io_server (msg, client->sock, async_context,
+                               request_finished, client);
 }
 
 static void
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ece9e95..93d0e9e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,9 +1,8 @@
 libsoup/soup-body-input-stream.c
 libsoup/soup-cache-input-stream.c
 libsoup/soup-converter-wrapper.c
-libsoup/soup-message-client-io.c
+libsoup/soup-http1-channel.c
 libsoup/soup-message-io.c
-libsoup/soup-message-server-io.c
 libsoup/soup-request.c
 libsoup/soup-server.c
 libsoup/soup-session.c


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