Author: rhuijben Date: Fri Nov 20 22:09:46 2015 New Revision: 1715431 URL: http://svn.apache.org/viewvc?rev=1715431&view=rev Log: Allow a response to handle multiple sets of headers for a single request. This enables support for http statee like 100 'Continue'.
This also adds the foundation for enabling an improved auth request handling, where in the future 401 and 407 responses will be handled on the request itself by doing some bucket tricks below the response, to avoid destroying and rescheduling requests. At the http protocol layers there will still be rescheduled requests (of course), but only a single response (attached via a serf_request_t) and its handlers will handle all responses of the request, as if it is a single response. Once this system works, the same system can easily handle the 101 'Upgrade' status for http/1.1 -> h2c as well. And then one serf_request_t can be used to refer to a request in the entire pipeline * buckets/response_buckets.c (response_context_t): Hold two sets of headers. (serf_bucket_response_create): Tweak init. (serf_bucket_response_get_headers): Get the fetch headers. (serf_response_destroy_and_data): Update cleanup. (parse_status_line): Allow parsing another statusline. (fetch_headers): Store new headers in incoming headers. (run_machine): Handle new states. (serf_bucket_response_wait_for_some_headers): New function. (serf_response_full_become_aggregate): Update usage. * serf_bucket_types.h (serf_bucket_response_wait_for_some_headers): New function. * test/test_buckets.c (test_response_no_body_expected): Tweak expectations. (test_response_continue): New function. Modified: serf/trunk/buckets/response_buckets.c serf/trunk/serf_bucket_types.h serf/trunk/test/test_buckets.c Modified: serf/trunk/buckets/response_buckets.c URL: http://svn.apache.org/viewvc/serf/trunk/buckets/response_buckets.c?rev=1715431&r1=1715430&r2=1715431&view=diff ============================================================================== --- serf/trunk/buckets/response_buckets.c (original) +++ serf/trunk/buckets/response_buckets.c Fri Nov 20 22:09:46 2015 @@ -29,10 +29,12 @@ typedef struct response_context_t { serf_bucket_t *stream; serf_bucket_t *body; /* Pointer to the stream wrapping the body. */ - serf_bucket_t *headers; /* holds parsed headers */ + serf_bucket_t *incoming_headers; /* holds parsed headers */ + serf_bucket_t *fetch_headers; /* the current set of headers */ enum { STATE_STATUS_LINE, /* reading status line */ + STATE_NEXT_STATUS_LINE, STATE_HEADERS, /* reading headers */ STATE_BODY, /* reading body */ STATE_TRAILERS, /* reading trailers */ @@ -63,10 +65,6 @@ static int expect_body(response_context_ if (ctx->head_req) return 0; - /* 100 Continue and 101 Switching Protocols */ - if (ctx->sl.code >= 100 && ctx->sl.code < 200) - return 0; - /* 204 No Content */ if (ctx->sl.code == 204) return 0; @@ -89,13 +87,15 @@ serf_bucket_t *serf_bucket_response_crea ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); ctx->stream = stream; ctx->body = NULL; - ctx->headers = serf_bucket_headers_create(allocator); + ctx->incoming_headers = serf_bucket_headers_create(allocator); + ctx->fetch_headers = ctx->incoming_headers; ctx->state = STATE_STATUS_LINE; ctx->chunked = 0; ctx->head_req = 0; ctx->decode_content = TRUE; ctx->error_on_eof = 0; ctx->config = NULL; + ctx->sl.reason = NULL; serf_linebuf_init(&ctx->linebuf); @@ -121,7 +121,7 @@ void serf_bucket_response_decode_content serf_bucket_t *serf_bucket_response_get_headers( serf_bucket_t *bucket) { - return ((response_context_t *)bucket->data)->headers; + return ((response_context_t *)bucket->data)->fetch_headers; } @@ -129,14 +129,18 @@ static void serf_response_destroy_and_da { response_context_t *ctx = bucket->data; - if (ctx->state != STATE_STATUS_LINE) { + if (ctx->sl.reason) { serf_bucket_mem_free(bucket->allocator, (void*)ctx->sl.reason); } serf_bucket_destroy(ctx->stream); if (ctx->body != NULL) serf_bucket_destroy(ctx->body); - serf_bucket_destroy(ctx->headers); + + if (ctx->incoming_headers) + serf_bucket_destroy(ctx->incoming_headers); + if (ctx->fetch_headers && ctx->fetch_headers != ctx->incoming_headers) + serf_bucket_destroy(ctx->fetch_headers); serf_default_destroy_and_data(bucket); } @@ -152,6 +156,11 @@ static apr_status_t parse_status_line(re int res; char *reason; /* ### stupid APR interface makes this non-const */ + if (ctx->sl.reason) { + serf_bucket_mem_free(allocator, (void*)ctx->sl.reason); + ctx->sl.reason = NULL; + } + /* ctx->linebuf.line should be of form: 'HTTP/1.1 200 OK', but we also explicitly allow the forms 'HTTP/1.1 200' (no reason) and 'HTTP/1.1 401.1 Logon failed' (iis extended error codes) @@ -223,7 +232,7 @@ static apr_status_t fetch_headers(serf_b /* Always copy the headers (from the linebuf into new mem). */ /* ### we should be able to optimize some mem copies */ serf_bucket_headers_setx( - ctx->headers, + ctx->incoming_headers, ctx->linebuf.line, end_key - ctx->linebuf.line, 1, c, ctx->linebuf.line + ctx->linebuf.used - c, 1); } @@ -245,6 +254,7 @@ static apr_status_t run_machine(serf_buc switch (ctx->state) { case STATE_STATUS_LINE: + case STATE_NEXT_STATUS_LINE: /* RFC 2616 says that CRLF is the only line ending, but we can easily * accept any kind of line ending. */ @@ -296,10 +306,25 @@ static apr_status_t run_machine(serf_buc int chunked = 0; int gzip = 0; + if (ctx->fetch_headers != ctx->incoming_headers) { + /* We now only have one interesting set of headers remaining */ + serf_bucket_destroy(ctx->fetch_headers); + ctx->fetch_headers = ctx->incoming_headers; + } + + if (ctx->sl.code >= 100 && ctx->sl.code < 200) { + /* We received a set of informational headers. + + Prepare for the next set */ + ctx->incoming_headers = serf_bucket_headers_create( + bkt->allocator); + ctx->state = STATE_NEXT_STATUS_LINE; + break; + } /* Advance the state. */ ctx->state = STATE_BODY; - /* If this is a response to a HEAD request, or code 1xx, 204 or 304 + /* If this is a response to a HEAD request, or 204 or 304 then we don't receive a real body. */ if (!expect_body(ctx)) { ctx->body = serf_bucket_simple_create(NULL, 0, NULL, NULL, @@ -312,7 +337,8 @@ static apr_status_t run_machine(serf_buc serf_bucket_barrier_create(ctx->stream, bkt->allocator); /* Are we chunked, C-L, or conn close? */ - v = serf_bucket_headers_get(ctx->headers, "Transfer-Encoding"); + v = serf_bucket_headers_get(ctx->fetch_headers, + "Transfer-Encoding"); /* Need a copy cuz we're going to write NUL characters into the string. */ @@ -344,7 +370,8 @@ static apr_status_t run_machine(serf_buc length via Transfer-Encoding chunked, when both chunked and Content-Length are passed */ - v = serf_bucket_headers_get(ctx->headers, "Content-Length"); + v = serf_bucket_headers_get(ctx->fetch_headers, + "Content-Length"); if (v) { apr_uint64_t length; length = apr_strtoi64(v, NULL, 10); @@ -365,7 +392,8 @@ static apr_status_t run_machine(serf_buc serf_bucket_set_config(ctx->body, ctx->config); } - v = serf_bucket_headers_get(ctx->headers, "Content-Encoding"); + v = serf_bucket_headers_get(ctx->fetch_headers, + "Content-Encoding"); if (v && ctx->decode_content) { /* Need to handle multiple content-encoding. */ if (v && strcasecmp("gzip", v) == 0) { @@ -434,6 +462,47 @@ apr_status_t serf_bucket_response_wait_f return wait_for_body(bucket, ctx); } +apr_status_t serf_bucket_response_wait_for_some_headers( + serf_bucket_t *bucket, + int wait_for_next) +{ + response_context_t *ctx = bucket->data; + + if (ctx->incoming_headers != ctx->fetch_headers) { + /* We have a good set of informational + headers */ + + if (!wait_for_next) + return APR_SUCCESS; + + /* We stop caring about a previous set, if there is one */ + serf_bucket_destroy(ctx->fetch_headers); + ctx->fetch_headers = ctx->incoming_headers; + + /* And fixup the state if we just read this one to avoid + theoretically returning success again */ + if (ctx->state == STATE_NEXT_STATUS_LINE) + ctx->state = STATE_STATUS_LINE; + } + + /* Keep reading and moving until we are in BODY or + STATE_NEXT_STATUS_LINE */ + while (ctx->state != STATE_BODY + && ctx->state != STATE_NEXT_STATUS_LINE) { + + apr_status_t status = run_machine(bucket, ctx); + + /* Anything other than APR_SUCCESS means that we cannot immediately + * read again (for now). + */ + if (status) + return status; + } + + /* in STATE_BODY or STATE_NEXT_STATUS_LINE */ + return APR_SUCCESS; +} + apr_status_t serf_bucket_response_status( serf_bucket_t *bkt, serf_status_line *sline) @@ -601,7 +670,7 @@ apr_status_t serf_response_full_become_a serf_bucket_aggregate_append(bucket, bkt); /* Add headers and stream buckets in order. */ - serf_bucket_aggregate_append(bucket, ctx->headers); + serf_bucket_aggregate_append(bucket, ctx->fetch_headers); serf_bucket_aggregate_append(bucket, ctx->stream); if (ctx->body != NULL) Modified: serf/trunk/serf_bucket_types.h URL: http://svn.apache.org/viewvc/serf/trunk/serf_bucket_types.h?rev=1715431&r1=1715430&r2=1715431&view=diff ============================================================================== --- serf/trunk/serf_bucket_types.h (original) +++ serf/trunk/serf_bucket_types.h Fri Nov 20 22:09:46 2015 @@ -150,6 +150,22 @@ apr_status_t serf_bucket_response_wait_f serf_bucket_t *response); /** + * Wait for the first HTTP headers to be processed for a @a response. + * If 1XX informational responses are received before the actual headers + * this function will return APR_SUCCESS as soon as such a set is processed, + * while serf_bucket_response_wait_for_headers() will wait until the + * actual headers to be available. + * + * If @a wait_for_next is TRUE, the function will wait for the next set + * of informational header instead of returning success for the first set. + * + * @since New in 1.4. + */ +apr_status_t serf_bucket_response_wait_for_some_headers( + serf_bucket_t *response, + int wait_for_next); + +/** * Get the headers bucket for @a response. */ serf_bucket_t *serf_bucket_response_get_headers( Modified: serf/trunk/test/test_buckets.c URL: http://svn.apache.org/viewvc/serf/trunk/test/test_buckets.c?rev=1715431&r1=1715430&r2=1715431&view=diff ============================================================================== --- serf/trunk/test/test_buckets.c (original) +++ serf/trunk/test/test_buckets.c Fri Nov 20 22:09:46 2015 @@ -1392,8 +1392,12 @@ static void test_response_no_body_expect status = read_all(bkt, buf, sizeof(buf), &len); - CuAssertIntEquals(tc, APR_EOF, status); - CuAssertIntEquals(tc, 0, len); + if (i == 0) { + /* blablablablabla is parsed as the next status line */ + CuAssertIntEquals(tc, SERF_ERROR_BAD_HTTP_RESPONSE, status); + } + else + CuAssertIntEquals(tc, 0, len); DRAIN_BUCKET(tmp); serf_bucket_destroy(bkt); @@ -1580,6 +1584,94 @@ static void test_random_eagain_in_respon } #undef BODY +static void test_response_continue(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + serf_bucket_t *bkt, *headers; + serf_bucket_alloc_t *alloc = test__create_bucket_allocator(tc, tb->pool); + const char long_response[] = + "HTTP/1.1 100 Continue" CRLF + "H: 1" CRLF + "Foo: Bar" CRLF + CRLF + "HTTP/1.1 109 Welcome to HTTP-9" CRLF + "Connection: Upgrade" CRLF + "H: 2" CRLF + "Upgrade: h9c" CRLF + CRLF + "HTTP/9.0 200 OK" CRLF + "Content-Type: text/plain" CRLF + "Content-Length: 26" CRLF + "H: 3" CRLF + CRLF + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + CRLF + CRLF; + + /* 1: First verify that we just read the body*/ + bkt = SERF_BUCKET_SIMPLE_STRING(long_response, alloc); + bkt = serf_bucket_response_create(bkt, alloc); + + read_and_check_bucket(tc, bkt, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + serf_bucket_destroy(bkt); + + /* 2: Check the headers the normal way */ + bkt = SERF_BUCKET_SIMPLE_STRING(long_response, alloc); + bkt = serf_bucket_response_create(bkt, alloc); + + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_headers(bkt)); + headers = serf_bucket_response_get_headers(bkt); + /* Verify that we just have the final set */ + CuAssertStrEquals(tc, "3", serf_bucket_headers_get(headers, "H")); + CuAssertStrEquals(tc, NULL, serf_bucket_headers_get(headers, "Foo")); + read_and_check_bucket(tc, bkt, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + serf_bucket_destroy(bkt); + + /* 3: Fetch the separate headers */ + bkt = SERF_BUCKET_SIMPLE_STRING(long_response, alloc); + bkt = serf_bucket_response_create(bkt, alloc); + + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_some_headers(bkt, FALSE)); + headers = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, "1", serf_bucket_headers_get(headers, "H")); + + /* Again*/ + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_some_headers(bkt, FALSE)); + headers = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, "1", serf_bucket_headers_get(headers, "H")); + + /* Now fetch second set */ + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_some_headers(bkt, TRUE)); + headers = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, "2", serf_bucket_headers_get(headers, "H")); + + /* Now fetch final set */ + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_some_headers(bkt, TRUE)); + headers = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, "3", serf_bucket_headers_get(headers, "H")); + + /* Fetch same again */ + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_some_headers(bkt, TRUE)); + headers = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, "3", serf_bucket_headers_get(headers, "H")); + + CuAssertIntEquals(tc, APR_SUCCESS, + serf_bucket_response_wait_for_headers(bkt)); + + headers = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, "3", serf_bucket_headers_get(headers, "H")); + CuAssertStrEquals(tc, NULL, serf_bucket_headers_get(headers, "Foo")); + read_and_check_bucket(tc, bkt, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + serf_bucket_destroy(bkt); +} + + static void test_dechunk_buckets(CuTest *tc) { test_baton_t *tb = tc->testBaton; @@ -2881,6 +2973,7 @@ CuSuite *test_buckets(void) SUITE_ADD_TEST(suite, test_response_bucket_peek_at_headers); SUITE_ADD_TEST(suite, test_response_bucket_iis_status_code); SUITE_ADD_TEST(suite, test_response_bucket_no_reason); + SUITE_ADD_TEST(suite, test_response_continue); SUITE_ADD_TEST(suite, test_bucket_header_set); SUITE_ADD_TEST(suite, test_bucket_header_do); SUITE_ADD_TEST(suite, test_iovec_buckets);