Author: rhuijben
Date: Mon Oct 19 16:13:10 2015
New Revision: 1709439
URL: http://svn.apache.org/viewvc?rev=1709439&view=rev
Log:
Add another piece of the HTTP/2 puzzle: a very simple HPACK encoder,
and a bit of plumbing to allow converting a HTTP/1.1 style request to
a HPACK set.
* serf-dev/dev/buckets/hpack_buckets.c
(serf_hpack_item_t,
serf_hpack_context_t): New struct.
(hpack_copy_from_headers,
serf__bucket_hpack_create_from_request,
serf_bucket_hpack_create,
serf_bucket_hpack_setc,
serf_bucket_hpack_setx,
hpack_int,
serialize,
serf_hpack_read,
serf_hpack_read_iovec,
serf_hpack_peek,
serf_hpack_destroy_and_data): New functions.
(serf_bucket_type_hpack): New bucket definition.
* serf-dev/dev/buckets/request_buckets.c
(serf__bucket_request_read): New function.
* serf-dev/dev/serf_private.h
(serf__bucket_request_read): New function.
(serf__bucket_hpack_create_from_request): New function.
* serf-dev/dev/test/test_buckets.c
(test_hpack_header_encode): New function.
(test_buckets): Add test_hpack_header_encode.
Modified:
serf/trunk/buckets/hpack_buckets.c
serf/trunk/buckets/request_buckets.c
serf/trunk/serf_private.h
serf/trunk/test/test_buckets.c
Modified: serf/trunk/buckets/hpack_buckets.c
URL:
http://svn.apache.org/viewvc/serf/trunk/buckets/hpack_buckets.c?rev=1709439&r1=1709438&r2=1709439&view=diff
==============================================================================
--- serf/trunk/buckets/hpack_buckets.c (original)
+++ serf/trunk/buckets/hpack_buckets.c Mon Oct 19 16:13:10 2015
@@ -239,3 +239,317 @@ serf__hpack_huffman_encode(const char *t
return APR_SUCCESS;
}
+
+/* ==================================================================== */
+
+typedef struct serf_hpack_item_t
+{
+ const char *key;
+ apr_size_t key_len;
+ const char *value;
+ apr_size_t value_len;
+
+ struct serf_hpack_item_t *next;
+ struct serf_hpack_item_t *prev;
+} serf_hpack_item_t;
+
+typedef struct serf_hpack_context_t
+{
+ serf_bucket_alloc_t *alloc;
+
+ serf_hpack_item_t *first;
+ serf_hpack_item_t *last;
+} serf_hpack_context_t;
+
+static apr_status_t
+hpack_copy_from_headers(void *baton,
+ const char *key,
+ const char *value)
+{
+ serf_bucket_t *hpack = baton;
+
+ if (!strcasecmp(key, "Host")
+ || !strcasecmp(key, "Connection")
+ || !strncasecmp(key, "Connection-", 11))
+ {
+ return APR_SUCCESS;
+ }
+
+ serf_bucket_hpack_setc(hpack, key, value);
+
+ return APR_SUCCESS;
+}
+
+
+apr_status_t
+serf__bucket_hpack_create_from_request(serf_bucket_t **new_hpack_bucket,
+ serf_hpack_table_t *hpack_table,
+ serf_bucket_t *request,
+ const char *scheme,
+ serf_bucket_alloc_t *allocator)
+{
+ const char *uri, *method, *host;
+
+ serf_bucket_t *hpack = serf_bucket_hpack_create(hpack_table, allocator);
+
+ serf_bucket_t *headers = serf_bucket_request_get_headers(request);
+
+ host = serf_bucket_headers_get(headers, "Host");
+
+ serf__bucket_request_read(request, NULL, &uri, &method);
+
+ serf_bucket_hpack_setc(hpack, ":method", method);
+ serf_bucket_hpack_setc(hpack, ":scheme", scheme);
+ serf_bucket_hpack_setc(hpack, ":authority", host);
+ serf_bucket_hpack_setc(hpack, ":path", uri);
+
+ serf_bucket_headers_do(headers, hpack_copy_from_headers, hpack);
+
+ *new_hpack_bucket = hpack;
+
+ return APR_SUCCESS;
+}
+
+
+serf_bucket_t *
+serf_bucket_hpack_create(serf_hpack_table_t *hpack_table,
+ serf_bucket_alloc_t *allocator)
+
+{
+ serf_hpack_context_t *ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
+
+ ctx->alloc = allocator;
+ ctx->first = ctx->last = NULL;
+
+ return serf_bucket_create(&serf_bucket_type_hpack, allocator, ctx);
+}
+
+void serf_bucket_hpack_setc(serf_bucket_t *hpack_bucket,
+ const char *key,
+ const char *value)
+{
+ serf_bucket_hpack_setx(hpack_bucket,
+ key, strlen(key), TRUE,
+ value, strlen(value), TRUE);
+}
+
+void serf_bucket_hpack_setx(serf_bucket_t *hpack_bucket,
+ const char *key,
+ apr_size_t key_size,
+ int header_copy,
+ const char *value,
+ apr_size_t value_size,
+ int value_copy)
+{
+ serf_hpack_context_t *ctx = hpack_bucket->data;
+ serf_hpack_item_t *hi;
+
+ for (hi = ctx->first; hi; hi = hi->next)
+ {
+ if (key_size == hi->key_len
+ && !strncasecmp(key, hi->key, key_size))
+ {
+ break;
+ }
+ }
+
+ /* TODO: Handle *_copy by keeping some flags */
+
+ if (hi && value[0] == ':')
+ {
+ serf_bucket_mem_free(ctx->alloc, (void*)hi->value);
+ hi->value = serf_bstrmemdup(ctx->alloc, value, value_size);
+ hi->value_len = value_size;
+
+ return;
+ }
+ else if (hi)
+ {
+ /* We probably want to allow duplicate *and* join behavior? */
+ }
+
+ hi = serf_bucket_mem_alloc(ctx->alloc, sizeof(*hi));
+
+ /* Convert keys to lower case as in RFC? Or keep case for
+ 1.1 like compatibility */
+
+ hi->key = serf_bstrmemdup(ctx->alloc, key, key_size);
+ hi->key_len = key_size;
+ hi->value = serf_bstrmemdup(ctx->alloc, value, value_size);
+ hi->value_len = value_size;
+
+ hi->prev = ctx->last;
+ hi->next = NULL;
+ if (ctx->last)
+ {
+ ctx->last->next = hi;
+ ctx->last = hi;
+ }
+ else
+ ctx->first = ctx->last = hi;
+}
+
+static void hpack_int(unsigned char flags, int bits, apr_uint64_t value, char
to[10], apr_size_t *used)
+{
+ unsigned char max_direct;
+ flags = flags & ~((1 << bits) - 1);
+ apr_size_t u;
+
+ max_direct = (unsigned char)(((apr_uint16_t)1 << bits) - 1);
+
+ if (value < max_direct)
+ {
+ to[0] = flags | (unsigned char)value;
+ *used = 1;
+ return;
+ }
+
+ to[0] = flags | max_direct;
+ value -= max_direct;
+ u = 1;
+
+ while (value >= 0x80)
+ {
+ to[u++] = (value & 0x7F) | 0x80;
+ value >>= 7;
+ }
+
+ to[u++] = (unsigned char)value;
+ *used = u;
+}
+
+static apr_status_t
+serialize(serf_bucket_t *bucket)
+{
+ /* Quick and dirty write out headers for V2
+
+ Needs A LOT of improvement.
+
+ Currently implements a complete in memory copy to the least
+ efficient HTTP2 / HPACK header format
+ */
+ serf_hpack_context_t *ctx = bucket->data;
+ serf_bucket_alloc_t *alloc = ctx->alloc;
+
+ /* Put on our aggregate bucket cloak */
+ serf_bucket_aggregate_become(bucket);
+
+ serf_hpack_item_t *hi;
+ serf_hpack_item_t *next;
+
+ for (hi = ctx->first; hi; hi = next)
+ {
+ char intbuf[10];
+ apr_size_t intbuf_len;
+
+ next = hi->next;
+
+ /* Literal header, no indexing (=has a name) */
+ hpack_int(0x40, 6, 0, intbuf, &intbuf_len);
+
+ serf_bucket_aggregate_append(bucket,
+ serf_bucket_simple_copy_create(intbuf, intbuf_len, alloc));
+
+ /* Name is literal, no huffman encoding */
+ hpack_int(0, 7, hi->key_len, intbuf, &intbuf_len);
+ serf_bucket_aggregate_append(bucket,
+ serf_bucket_simple_copy_create(intbuf, intbuf_len, alloc));
+
+ serf_bucket_aggregate_append(bucket,
+ serf_bucket_simple_own_create(hi->key, hi->key_len, alloc));
+
+ /* Value is literal, no huffman encoding */
+ hpack_int(0, 7, hi->value_len, intbuf, &intbuf_len);
+ serf_bucket_aggregate_append(bucket,
+ serf_bucket_simple_copy_create(intbuf, intbuf_len, alloc));
+
+ serf_bucket_aggregate_append(bucket,
+ serf_bucket_simple_own_create(hi->value, hi->value_len, alloc));
+
+ /* We handed ownership of key and value, so we only have to free item */
+ serf_bucket_mem_free(alloc, hi);
+ }
+ ctx->first = ctx->last = NULL;
+
+ serf_bucket_mem_free(alloc, ctx);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t
+serf_hpack_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data,
+ apr_size_t *len)
+{
+ apr_status_t status = serialize(bucket);
+
+ if (status)
+ return status;
+
+ return serf_bucket_read(bucket, requested, data, len);
+}
+
+apr_status_t
+serf_hpack_read_iovec(serf_bucket_t *bucket,
+ apr_size_t requested,
+ int vecs_size,
+ struct iovec *vecs,
+ int *vecs_used)
+{
+ apr_status_t status = serialize(bucket);
+
+ if (status)
+ return status;
+
+ return serf_bucket_read_iovec(bucket, requested, vecs_size, vecs, vecs_used);
+}
+
+static apr_status_t
+serf_hpack_peek(serf_bucket_t *bucket,
+ const char **data,
+ apr_size_t *len)
+{
+ apr_status_t status = serialize(bucket);
+
+ if (status)
+ return status;
+
+ return serf_bucket_peek(bucket, data, len);
+}
+
+static void
+serf_hpack_destroy_and_data(serf_bucket_t *bucket)
+{
+ serf_hpack_context_t *ctx = bucket->data;
+ serf_hpack_item_t *hi;
+ serf_hpack_item_t *next;
+
+ for (hi = ctx->first; hi; hi = next)
+ {
+ next = hi->next;
+
+ /* TODO: Implement conditional free */
+
+ serf_bucket_mem_free(ctx->alloc, (char*)hi->key);
+ serf_bucket_mem_free(ctx->alloc, (char*)hi->value);
+ serf_bucket_mem_free(ctx->alloc, hi);
+ }
+
+ serf_default_destroy_and_data(bucket);
+}
+
+
+/* ### need to implement */
+#define serf_hpack_readline NULL
+
+const serf_bucket_type_t serf_bucket_type_hpack = {
+ "HPACK",
+ serf_hpack_read,
+ serf_hpack_readline,
+ serf_hpack_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ serf_hpack_peek,
+ serf_hpack_destroy_and_data,
+};
Modified: serf/trunk/buckets/request_buckets.c
URL:
http://svn.apache.org/viewvc/serf/trunk/buckets/request_buckets.c?rev=1709439&r1=1709438&r2=1709439&view=diff
==============================================================================
--- serf/trunk/buckets/request_buckets.c (original)
+++ serf/trunk/buckets/request_buckets.c Mon Oct 19 16:13:10 2015
@@ -71,6 +71,22 @@ serf_bucket_t *serf_bucket_request_get_h
return ((request_context_t *)bucket->data)->headers;
}
+void serf__bucket_request_read(serf_bucket_t *request_bucket,
+ serf_bucket_t **body_bkt,
+ const char **uri,
+ const char **method)
+{
+ request_context_t *ctx = request_bucket->data;
+
+ if (body_bkt)
+ *body_bkt = ctx->body;
+ if (uri)
+ *uri = ctx->uri;
+ if (method)
+ *method = ctx->method;
+}
+
+
void serf_bucket_request_set_root(
serf_bucket_t *bucket,
const char *root_url)
Modified: serf/trunk/serf_private.h
URL:
http://svn.apache.org/viewvc/serf/trunk/serf_private.h?rev=1709439&r1=1709438&r2=1709439&view=diff
==============================================================================
--- serf/trunk/serf_private.h (original)
+++ serf/trunk/serf_private.h Mon Oct 19 16:13:10 2015
@@ -447,6 +447,21 @@ void serf__bucket_response_set_error_on_
void serf__bucket_headers_remove(serf_bucket_t *headers_bucket,
const char *header);
+/**
+ * Read raw information stored in request REQUEST_BUCKET. All output values
+ * are directly copied from the internal state.
+ *
+ * Output values can be passed as NULL when not interested.
+ */
+void serf__bucket_request_read(serf_bucket_t *request_bucket,
+ serf_bucket_t **body_bkt,
+ const char **uri,
+ const char **method);
+
+serf_bucket_t *serf_bucket_request_get_body(
+ serf_bucket_t *bucket);
+
+
/*** Authentication handler declarations ***/
typedef enum { PROXY, HOST } peer_t;
@@ -536,6 +551,13 @@ apr_status_t serf__hpack_huffman_encode(
unsigned char *encoded,
apr_size_t *encoded_len);
+apr_status_t serf__bucket_hpack_create_from_request(
+ serf_bucket_t **new_hpack_bucket,
+ serf_hpack_table_t *hpack_table,
+ serf_bucket_t *request,
+ const char *scheme,
+ serf_bucket_alloc_t *allocator);
+
/* From connection_request.c */
void serf__link_requests(serf_request_t **list, serf_request_t **tail,
serf_request_t *request);
Modified: serf/trunk/test/test_buckets.c
URL:
http://svn.apache.org/viewvc/serf/trunk/test/test_buckets.c?rev=1709439&r1=1709438&r2=1709439&view=diff
==============================================================================
--- serf/trunk/test/test_buckets.c (original)
+++ serf/trunk/test/test_buckets.c Mon Oct 19 16:13:10 2015
@@ -2125,6 +2125,33 @@ static void test_hpack_huffman_encode(Cu
}
#undef VERIFY_REVERSE
+static void test_hpack_header_encode(CuTest *tc)
+{
+ test_baton_t *tb = tc->testBaton;
+ serf_bucket_alloc_t *alloc;
+ serf_bucket_t *hpack;
+ char resultbuffer[1024];
+ apr_size_t sz;
+
+ alloc = serf_bucket_allocator_create(tb->pool, NULL, NULL);
+
+ hpack = serf_bucket_hpack_create(NULL, alloc);
+
+ CuAssertTrue(tc, SERF_BUCKET_IS_HPACK(hpack));
+
+ serf_bucket_hpack_setc(hpack, ":method", "PUT");
+ serf_bucket_hpack_setc(hpack, ":scheme", "https");
+ serf_bucket_hpack_setc(hpack, ":path", "/");
+ serf_bucket_hpack_setc(hpack, ":authority", "localhost");
+
+ CuAssertIntEquals(tc, APR_EOF,
+ read_all(hpack, resultbuffer, sizeof(resultbuffer), &sz));
+
+ /* CuAssertTrue(tc, ! SERF_BUCKET_IS_HPACK(hpack)); */
+ CuAssertTrue(tc, sz > 4);
+ CuAssertTrue(tc, sz <= 59); /* The all literal approach takes 59 bytes */
+}
+
CuSuite *test_buckets(void)
{
CuSuite *suite = CuSuiteNew();
@@ -2158,6 +2185,7 @@ CuSuite *test_buckets(void)
SUITE_ADD_TEST(suite, test_http2_unpad_buckets);
SUITE_ADD_TEST(suite, test_hpack_huffman_decode);
SUITE_ADD_TEST(suite, test_hpack_huffman_encode);
+ SUITE_ADD_TEST(suite, test_hpack_header_encode);
#if 0
/* This test for issue #152 takes a lot of time generating 4GB+ of random
data so it's disabled by default. */