summaryrefslogtreecommitdiff
path: root/libsoup/soup-http2-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/soup-http2-connection.c')
-rw-r--r--libsoup/soup-http2-connection.c248
1 files changed, 248 insertions, 0 deletions
diff --git a/libsoup/soup-http2-connection.c b/libsoup/soup-http2-connection.c
new file mode 100644
index 00000000..8935b62c
--- /dev/null
+++ b/libsoup/soup-http2-connection.c
@@ -0,0 +1,248 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-http2-connection.c
+ *
+ * Copyright 2014 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <nghttp2/nghttp2.h>
+
+#include "soup-http2-connection.h"
+
+G_DEFINE_TYPE (SoupHTTP2Connection, soup_http2_connection, G_TYPE_OBJECT)
+
+typedef struct {
+ nghttp2_session *session;
+
+ GInputStream *istream;
+ GPollableInputStream *poll_istream;
+ GOutputStream *ostream;
+ GPollableOutputStream *poll_ostream;
+
+} SoupHTTP2ConnectionPrivate;
+#define SOUP_HTTP2_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_HTTP2_CONNECTION, SoupHTTP2ConnectionPrivate))
+
+static ssize_t
+ngh2_send_cb (nghttp2_session *session,
+ const uint8_t *data, size_t length,
+ int flags, void *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ gssize nwrote;
+ GError *error = NULL;
+
+ nwrote = g_pollable_output_stream_write_nonblocking (priv->ostream,
+ buf, length,
+ NULL, &error);
+ if (nwrote == -1) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+ g_error_free (error);
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ g_error_free (error);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ } else
+ return nread;
+}
+
+static ssize_t
+ngh_recv_cb (nghttp2_session *session,
+ uint8_t *buf, size_t length,
+ int flags, void *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ gssize nread;
+ GError *error = NULL;
+
+ nread = g_pollable_input_stream_read_nonblocking (priv->istream,
+ buf, length,
+ NULL, &error);
+ if (nread == -1) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+ g_error_free (error);
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ g_error_free (error);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ } else if (nread == 0)
+ return NGHTTP2_ERR_EOF;
+ else
+ return nread;
+}
+
+static int
+ngh2_data_chunk_recv_cb (nghttp2_session *session, uint8_t flags,
+ int32_t stream_id, const uint8_t *data, size_t len,
+ void *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ SoupHTTP2Channel *channel;
+
+ channel = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (stream_id));
+ if (!channel)
+ return -1;
+
+ soup_http2_channel_push_data (channel, data, len);
+ return 0;
+}
+
+static int
+ngh2_stream_close_cb (nghttp2_session *session,
+ int32_t stream_id, uint32_t error_code,
+ void *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ SoupHTTP2Channel *channel;
+
+ channel = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (stream_id));
+ if (channel) {
+ soup_http2_channel_input_closed (channel, error_code);
+ if (soup_http2_channel_get_output_closed (channel))
+ g_hash_table_remove (GINT_TO_POINTER (stream_id));
+ }
+ return 0;
+}
+
+static int
+ngh2_begin_headers_cb (nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ SoupHTTP2Channel *channel;
+
+ channel = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (stream_id));
+ if (channel)
+ return -1;
+
+ if (frame->hd.type != NGHTTP2_HEADERS)
+ return -1;
+
+ channel = soup_http2_channel_new (conn, frame->hd.stream_id);
+ g_hash_table_insert (priv->channels, GINT_TO_POINTER (frame->hd.stream_id), channel);
+ return 0
+}
+
+static int
+ngh2_on_header_cb (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 *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ SoupHTTP2Channel *channel;
+
+ channel = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (stream_id));
+ if (!channel)
+ return -1;
+
+ if (soup_http2_channel_get_headers_complete (channel))
+ return 0;
+
+ if (!nghttp2_check_header_value (value, valuelen))
+ return -1;
+ if (namelen == 0 ||
+ (name[0] == ':' && !nghttp2_check_header_name (name + 1, namelen - 1)) ||
+ (name[0] != ':' && !nghttp2_check_header_name (name, namelen)))
+ return -1;
+
+ soup_http2_channel_push_header (channel,
+ name, namelen,
+ value, valuelen);
+ return 0;
+}
+
+static int
+ngh2_frame_recv_cb (nghttp2_session *session,
+ const nghttp2_frame *frame,
+ void *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (conn);
+ SoupHTTP2Channel *channel;
+
+ if (frame->hd.type != NGHTTP2_HEADERS)
+ return 0;
+
+ channel = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (stream_id));
+ if (!channel)
+ return 0;
+
+ soup_http2_channel_set_headers_complete (channel);
+ return 0;
+}
+
+static void
+ soup_http2_connection_init (SoupHTTP2Connection *connection)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (connection);
+ nghttp2_session_callbacks *callbacks;
+
+ priv->channels = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+
+ nghttp2_session_callbacks_new (&callbacks);
+ nghttp2_session_callbacks_set_send_callback (callbacks, ngh2_send_cb);
+ nghttp2_session_callbacks_set_recv_callback (callbacks, ngh2_recv_cb);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback (callbacks, ngh2_data_chunk_recv_cb);
+ nghttp2_session_callbacks_set_on_stream_close_callback (callbacks, ngh2_stream_close_cb);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, ngh2_begin_headers_cb);
+ nghttp2_session_callbacks_set_on_header_callback (callbacks, ngh2_header_cb);
+ nghttp2_session_callbacks_set_on_frame_recv_callback (callbacks, ngh2_frame_recv_cb);
+
+ nghttp2_session_client_new (&priv->session, callbacks, connection);
+ nghttp2_session_callbacks_del (callbacks);
+}
+
+static void
+soup_http2_connection_run (SoupHTTP2Connection *conn)
+{
+ SoupHTTP2ConnectionPrivate *priv = SOUP_HTTP2_CONNECTION_GET_PRIVATE (connection);
+ GSource *in, *out;
+ GMainContext *ctx;
+ GMainLoop *loop;
+ int status;
+
+ ctx = g_main_context_new ();
+ g_main_context_push_thread_default (ctx);
+ loop = g_main_loop_new (ctx, FALSE);
+
+ in = g_pollable_input_stream_create_source (priv->istream, NULL);
+ g_source_set_callback (in, (GSourceFunc) quit_callback, loop);
+ g_source_attach (in, ctx);
+
+ out = g_pollable_output_stream_create_source (priv->ostream, NULL);
+ g_source_set_callback (out, (GSourceFunc) quit_callback, loop);
+ g_source_attach (out, ctx);
+
+ while (TRUE) {
+ if (g_pollable_output_stream_is_writable (priv->ostream)) {
+ status = nghttp2_session_send (priv->session);
+ if (status != 0)
+ break;
+ }
+ if (g_pollable_input_stream_is_readable (priv->ostream)) {
+ status = nghttp2_session_recv (priv->session);
+ if (status != 0)
+ break;
+ }
+
+ if (!nghttp2_session_want_read (priv->session) &&
+ !nghttp2_session_want_write (priv->session))
+ break;
+
+ g_main_loop_run (loop);
+ }
+
+ g_source_destroy (in);
+ g_source_unref (in);
+ g_source_destroy (out);
+ g_source_unref (out);
+
+ g_main_loop_unref (loop);
+ g_main_context_pop_thread_default (ctx);
+ g_main_context_unref (ctx);
+}
+