Author: rhuijben Date: Wed Oct 28 17:12:57 2015 New Revision: 1711068 URL: http://svn.apache.org/viewvc?rev=1711068&view=rev Log: Start abstracting http2 streams as a small step towards full http2 frame routing in the http2 protocol engine.
This patch fixes two bugs that broke running requests against nghttpd2 and httpd 2.4.17 with mod_http2. * protocols/http2_protocol.c (serf_http2_stream_t): Move to http2_protocol.h (serf_http2_protocol_t): Store some settings. (http2_protocol_cleanup): Release streams. (serf__http2_protocol_init): Handle new settings. Don't write yet, to allow h2direct handshakes. (setup_for_http2): Create stream and extract the rest of the code to serf_http2__stream_setup_request. (serf_http2__allocate_stream_id, move_to_head, serf_http2__stream_get): new function. * protocols/http2_protocol.h (http2_read): Fix size logging. Don't ack SETTING acks. Log reset reasons. * protocols/http2_stream.c New file. (serf_http2__stream_create, serf_http2__stream_cleanup, serf_http2__stream_setup_request): New function. Added: serf/trunk/protocols/http2_stream.c (with props) Modified: serf/trunk/protocols/http2_protocol.c serf/trunk/protocols/http2_protocol.h Modified: serf/trunk/protocols/http2_protocol.c URL: http://svn.apache.org/viewvc/serf/trunk/protocols/http2_protocol.c?rev=1711068&r1=1711067&r2=1711068&view=diff ============================================================================== --- serf/trunk/protocols/http2_protocol.c (original) +++ serf/trunk/protocols/http2_protocol.c Wed Oct 28 17:12:57 2015 @@ -127,37 +127,6 @@ serf_bucket_create_numberv(serf_bucket_a return serf_bucket_simple_own_create(buffer, sz, allocator); } - -struct serf_http2_stream_t -{ - struct serf_http2_protocol_t *ctx; - - /* Linked list of currently existing streams */ - struct serf_http2_stream_t *next; - struct serf_http2_stream_t *prev; - - serf_request_t *request; /* May be NULL as streams may outlive requests */ - - apr_int64_t lr_window; /* local->remote */ - apr_int64_t rl_window; /* remote->local */ - - /* -1 until allocated. Odd is client side initiated, even server side */ - apr_int32_t streamid; - - enum - { - H2S_IDLE = 0, - H2S_RESERVED_REMOTE, - H2S_RESERVED_LOCAL, - H2S_OPEN, - H2S_HALFCLOSED_REMOTE, - H2S_HALFCLOSED_LOCAL, - H2S_CLOSED - } status; - - /* TODO: Priority, etc. */ -}; - struct serf_http2_protocol_t { apr_pool_t *pool; @@ -166,6 +135,9 @@ struct serf_http2_protocol_t serf_hpack_table_t *hpack_tbl; + apr_uint32_t default_lr_window; + apr_uint32_t default_rl_window; + apr_int64_t lr_window; /* local->remote */ apr_int64_t rl_window; /* remote->local */ apr_int32_t next_local_streamid; @@ -186,7 +158,17 @@ static apr_status_t http2_protocol_cleanup(void *state) { serf_connection_t *conn = state; - /* serf_http2_protocol_t *ctx = conn->protocol_baton; */ + serf_http2_protocol_t *h2 = conn->protocol_baton; + serf_http2_stream_t *stream, *next; + + /* First clean out all streams */ + for (stream = h2->first; stream; stream = next) + { + next = stream->next; + serf_http2__stream_cleanup(stream); + } + + h2->first = h2->last = NULL; conn->protocol_baton = NULL; return APR_SUCCESS; @@ -204,8 +186,13 @@ void serf__http2_protocol_init(serf_conn ctx->pool = protocol_pool; ctx->conn = conn; ctx->ostream = conn->ostream_tail; - ctx->lr_window = HTTP2_DEFAULT_WINDOW_SIZE; - ctx->rl_window = HTTP2_DEFAULT_WINDOW_SIZE; + + /* Defaults until negotiated */ + ctx->default_lr_window = HTTP2_DEFAULT_WINDOW_SIZE; + ctx->default_rl_window = HTTP2_DEFAULT_WINDOW_SIZE; + + ctx->lr_window = ctx->default_lr_window; + ctx->rl_window = ctx->default_rl_window; ctx->next_local_streamid = 1; /* 2 if we would be the server */ ctx->next_remote_streamid = 2; /* 1 if we would be the client */ @@ -254,55 +241,33 @@ void serf__http2_protocol_init(serf_conn NULL, NULL, NULL, HTTP2_DEFAULT_MAX_FRAMESIZE, NULL, NULL, conn->allocator); - serf_http2__enqueue_frame(ctx, tmp, TRUE); + serf_http2__enqueue_frame(ctx, tmp, FALSE); } } /* Creates a HTTP/2 request from a serf request */ static apr_status_t -setup_for_http2(serf_http2_protocol_t *ctx, +setup_for_http2(serf_http2_protocol_t *h2, serf_request_t *request) { - apr_status_t status; - serf_bucket_t *hpack; - serf_bucket_t *body; - static apr_int32_t NEXT_frame = 1; + serf_http2_stream_t *stream; - apr_int32_t streamid = NEXT_frame; - NEXT_frame += 2; + stream = serf_http2__stream_create(h2, -1, + h2->default_lr_window, + h2->default_rl_window, + h2->conn->allocator); - if (!request->req_bkt) + if (h2->first) { - status = serf__setup_request(request); - if (status) - return status; + stream->next = h2->first; + h2->first->prev = stream; + h2->first = stream; } + else + h2->last = h2->first = stream; - serf__bucket_request_read(request->req_bkt, &body, NULL, NULL); - status = serf__bucket_hpack_create_from_request(&hpack, NULL, request->req_bkt, - request->conn->host_info.scheme, - request->allocator); - if (status) - return status; - - if (!body) - { - /* This destroys the body... Perhaps we should make an extract - and clear api */ - serf_bucket_destroy(request->req_bkt); - request->req_bkt = NULL; - } - - hpack = serf__bucket_http2_frame_create(hpack, HTTP2_FRAME_TYPE_HEADERS, - HTTP2_FLAG_END_STREAM - | HTTP2_FLAG_END_HEADERS, - &streamid, NULL, NULL, - HTTP2_DEFAULT_MAX_FRAMESIZE, - NULL, NULL, request->allocator); - - serf_http2__enqueue_frame(ctx, hpack, TRUE); - - return APR_SUCCESS; + return serf_http2__stream_setup_request(stream, h2->hpack_tbl, + request); } apr_status_t @@ -378,6 +343,7 @@ http2_read(serf_connection_t *conn) unsigned char flags; unsigned char frametype; apr_int32_t streamid; + apr_uint64_t size; status = serf__bucket_http2_unframe_read_info(ctx->cur_frame, &streamid, &frametype, @@ -386,9 +352,11 @@ http2_read(serf_connection_t *conn) if (status && !APR_STATUS_IS_EOF(status)) break; + size = serf_bucket_get_remaining(ctx->cur_frame); + serf__log(LOGLVL_INFO, SERF_LOGHTTP2, conn->config, "Start 0x%02x http2 frame on stream 0x%x, flags=0x%x, size=0x%x\n", - (int)frametype, (int)streamid, (int)flags, (int)ctx->buffer_used); + (int)frametype, (int)streamid, (int)flags, (int)size); ctx->in_payload = TRUE; @@ -454,7 +422,16 @@ http2_read(serf_connection_t *conn) (ctx->buffer_used >= 8) ? ctx->buffer_used-8 : 0)); - if (frametype == HTTP2_FRAME_TYPE_SETTINGS) + if (frametype == HTTP2_FRAME_TYPE_RST_STREAM && conn) + serf__log(LOGLVL_WARNING, SERF_LOGHTTP2, conn->config, + "Reset reason %d: %s\n", ctx->buffer[7], + apr_pstrmemdup(conn->pool, + &ctx->buffer[8], + (ctx->buffer_used >= 8) + ? ctx->buffer_used - 8 : 0)); + + if (frametype == HTTP2_FRAME_TYPE_SETTINGS + && !(flags & HTTP2_FLAG_ACK)) { /* Always ack settings */ serf_http2__enqueue_frame( @@ -609,3 +586,98 @@ http2_protocol_teardown(serf_connection_ apr_pool_destroy(ctx->pool); conn->protocol_baton = NULL; } + +apr_int32_t +serf_http2__allocate_stream_id(void *baton, + apr_int32_t *streamid) +{ + serf_http2_stream_t *stream = baton; + + /* Do we need to assign a new id? + + We do this when converting the frame to on-wire data, to avoid + creating frames out of order... which would make the other side + deny our frame. + */ + if (stream->streamid < 0) + { + stream->streamid = stream->h2->next_local_streamid; + stream->h2->next_local_streamid += 2; + + if (stream->status == H2S_INIT) + stream->status = H2S_IDLE; + } + + return stream->streamid; +} + +static void +move_to_head(serf_http2_stream_t *stream) +{ + /* Not implemented yet */ +} + +serf_http2_stream_t * +serf_http2__stream_get(serf_http2_protocol_t *h2, + apr_int32_t streamid, + int create_for_remote, + int move_first) +{ + serf_http2_stream_t *stream; + + if (streamid < 0) + return NULL; + + for (stream = h2->first; stream; stream->next) + { + if (stream->streamid == streamid) + { + if (move_first && stream != h2->first) + move_to_head(stream); + + return stream; + } + } + + if (create_for_remote + && (streamid & 0x01) == (h2->next_remote_streamid & 0x01)) + { + serf_http2_stream_t *rs; + stream = serf_http2__stream_create(h2, streamid, + h2->default_lr_window, + h2->default_rl_window, + h2->conn->allocator); + + if (h2->first) + { + stream->next = h2->first; + h2->first->prev = stream; + h2->first = stream; + } + else + h2->last = h2->first = stream; + + if (streamid < h2->next_remote_streamid) + stream->status = H2S_CLOSED; + else + h2->next_remote_streamid = (streamid + 2); + + for (rs = h2->first; rs; rs = rs->next) + { + if (rs->status <= H2S_IDLE + && rs->streamid < streamid + && (streamid & 0x01) == (rs->streamid & 0x01)) + { + /* https://tools.ietf.org/html/rfc7540#section-5.1.1 + The first use of a new stream identifier implicitly closes + all streams in the "idle" state that might have been + initiated by that peer with a lower-valued stream identifier. + */ + rs->status = H2S_CLOSED; + } + } + + return stream; + } + return NULL; +} Modified: serf/trunk/protocols/http2_protocol.h URL: http://svn.apache.org/viewvc/serf/trunk/protocols/http2_protocol.h?rev=1711068&r1=1711067&r2=1711068&view=diff ============================================================================== --- serf/trunk/protocols/http2_protocol.h (original) +++ serf/trunk/protocols/http2_protocol.h Wed Oct 28 17:12:57 2015 @@ -87,13 +87,79 @@ extern "C" { /* ------------------------------------- */ typedef struct serf_http2_protocol_t serf_http2_protocol_t; -typedef struct serf_http2_stream_t serf_http2_stream_t; +typedef struct serf_http2_stream_t +{ + struct serf_http2_protocol_t *h2; + serf_bucket_alloc_t *alloc; + /* Linked list of currently existing streams */ + struct serf_http2_stream_t *next; + struct serf_http2_stream_t *prev; + + serf_request_t *request; /* May be NULL as streams may outlive requests */ + + apr_int64_t lr_window; /* local->remote */ + apr_int64_t rl_window; /* remote->local */ + + /* -1 until allocated. Odd is client side initiated, even server side */ + apr_int32_t streamid; + + enum + { + H2S_INIT = 0, + H2S_IDLE, + H2S_RESERVED_REMOTE, + H2S_RESERVED_LOCAL, + H2S_OPEN, + H2S_HALFCLOSED_REMOTE, + H2S_HALFCLOSED_LOCAL, + H2S_CLOSED + } status; + + /* TODO: Priority, etc. */ +} serf_http2_stream_t; + +/* Enques an http2 frame for output */ apr_status_t serf_http2__enqueue_frame(serf_http2_protocol_t *h2, serf_bucket_t *frame, int pump); +/* Creates a new stream */ +serf_http2_stream_t * +serf_http2__stream_create(serf_http2_protocol_t *h2, + apr_int32_t streamid, + apr_uint32_t lr_window, + apr_uint32_t rl_window, + serf_bucket_alloc_t *alloc); + +/* Allocates a new stream id for a stream. + BATON is a serf_http2_stream_t * instance. + + Passed to serf__bucket_http2_frame_create when writing for + a stream. +*/ +apr_int32_t +serf_http2__allocate_stream_id(void *baton, + apr_int32_t *streamid); + +void +serf_http2__stream_cleanup(serf_http2_stream_t *stream); + +serf_http2_stream_t * +serf_http2__stream_get_by_id(serf_http2_protocol_t *h2, + apr_int32_t streamid, + int create_for_remote, + int move_first); + +/* Sets up STREAM to handle REQUEST */ +apr_status_t +serf_http2__stream_setup_request(serf_http2_stream_t *stream, + serf_hpack_table_t *hpack_tbl, + serf_request_t *request); + + + #ifdef __cplusplus } #endif Added: serf/trunk/protocols/http2_stream.c URL: http://svn.apache.org/viewvc/serf/trunk/protocols/http2_stream.c?rev=1711068&view=auto ============================================================================== --- serf/trunk/protocols/http2_stream.c (added) +++ serf/trunk/protocols/http2_stream.c Wed Oct 28 17:12:57 2015 @@ -0,0 +1,114 @@ +/* ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <stdlib.h> + +#include <apr_pools.h> + +#include "serf.h" +#include "serf_bucket_util.h" +#include "serf_private.h" + +#include "protocols/http2_buckets.h" +#include "protocols/http2_protocol.h" + +serf_http2_stream_t * +serf_http2__stream_create(serf_http2_protocol_t *h2, + apr_int32_t streamid, + apr_uint32_t lr_window, + apr_uint32_t rl_window, + serf_bucket_alloc_t *alloc) +{ + serf_http2_stream_t *stream = serf_bucket_mem_alloc(alloc, sizeof(*stream)); + + stream->h2 = h2; + stream->alloc = alloc; + + stream->next = stream->prev = NULL; + stream->request = NULL; + + stream->lr_window = lr_window; + stream->rl_window = rl_window; + + if (streamid >= 0) + stream->streamid = streamid; + else + stream->streamid = -1; /* Undetermined yet */ + + stream->status = (streamid >= 0) ? H2S_IDLE : H2S_INIT; + + return stream; +} + +void +serf_http2__stream_cleanup(serf_http2_stream_t *stream) +{ + serf_bucket_mem_free(stream->alloc, stream); +} + +apr_status_t +serf_http2__stream_setup_request(serf_http2_stream_t *stream, + serf_hpack_table_t *hpack_tbl, + serf_request_t *request) +{ + apr_status_t status; + serf_bucket_t *hpack; + serf_bucket_t *body; + + stream->request = request; + + if (!request->req_bkt) + { + status = serf__setup_request(request); + if (status) + return status; + } + + serf__bucket_request_read(request->req_bkt, &body, NULL, NULL); + status = serf__bucket_hpack_create_from_request(&hpack, hpack_tbl, + request->req_bkt, + request->conn->host_info.scheme, + request->allocator); + if (status) + return status; + + if (!body) + { + /* This destroys the body... Perhaps we should make an extract + and clear api */ + serf_bucket_destroy(request->req_bkt); + request->req_bkt = NULL; + } + + hpack = serf__bucket_http2_frame_create(hpack, HTTP2_FRAME_TYPE_HEADERS, + HTTP2_FLAG_END_STREAM + | HTTP2_FLAG_END_HEADERS, + &stream->streamid, + serf_http2__allocate_stream_id, + stream, + HTTP2_DEFAULT_MAX_FRAMESIZE, + NULL, NULL, request->allocator); + + serf_http2__enqueue_frame(stream->h2, hpack, TRUE); + + stream->status = H2S_OPEN; /* Headers sent */ + + return APR_SUCCESS; +} Propchange: serf/trunk/protocols/http2_stream.c ------------------------------------------------------------------------------ svn:eol-style = native