Author: rhuijben Date: Thu Oct 15 21:52:17 2015 New Revision: 1708898 URL: http://svn.apache.org/viewvc?rev=1708898&view=rev Log: Make the standard serf connections capable of delaying the initial request writing until the ALPN negotiation is complete, when explicitly requested.
This will allow developing http/2 support within the same connections. Currently this support is still completely disabled outside the test code. * outgoing.c (request_or_data_pending): Don't assume requests will be written if ther is no framing type. (serf__conn_update_pollset): Declare always interested in incoming data if not in http/1 mode. (reset_connection): Reset write_now. (read_from_connection): When write_now, do write now to handle switching to http/1.1. (serf_connection_create): Extend initialization. (serf_connection_set_framing_type): New function. * serf.h (serf_connection_framing_type_t): New enum. (serf_connection_set_framing_type): New function. * serf_private.h (serf_connection_t): Add two variables. * test/MockHTTPinC/MockHTTP.h (WithProtocol): New define. (mhSetServerProtocol): New function. * test/MockHTTPinC/MockHTTP_private.h (mhServCtx_t): Add variable. * test/MockHTTPinC/MockHTTP_server.c (set_server_protocol, mhSetServerProtocol): New functions. (alpn_select_callback): New function. (initSSLCtx): With new openssl, init alpn. * test/serf_get.c (app_baton_t): Add variable. (conn_baton_t): Add connection. (conn_set_protocol): New function. (conn_setup): Initialize negotiation if requested. (HTTP2FLAG): New define. (options): Add new option. For now #if 0-ed (main): Parse new argument. Store connection in baton. * test/test_ssl.c (openssl/applink.c): Wrap in a #pragma to suppress a few dozen warnings. (http11_select_protocol, http11_alpn_setup): New functions. (test_ssl_alpn_negotiate): New test. (test_ssl): Add test_ssl_alpn_negotiate. Modified: serf/trunk/outgoing.c serf/trunk/serf.h serf/trunk/serf_private.h serf/trunk/test/MockHTTPinC/MockHTTP.h serf/trunk/test/MockHTTPinC/MockHTTP_private.h serf/trunk/test/MockHTTPinC/MockHTTP_server.c serf/trunk/test/serf_get.c serf/trunk/test/test_ssl.c Modified: serf/trunk/outgoing.c URL: http://svn.apache.org/viewvc/serf/trunk/outgoing.c?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/outgoing.c (original) +++ serf/trunk/outgoing.c Thu Oct 15 21:52:17 2015 @@ -106,7 +106,8 @@ request_or_data_pending(serf_request_t * reqs_in_progress = conn->completed_requests - conn->completed_responses; /* Prepare the next request */ - if (conn->pipelining || (!conn->pipelining && reqs_in_progress == 0)) + if (conn->framing_type != SERF_CONNECTION_FRAMING_TYPE_NONE + && (conn->pipelining || (!conn->pipelining && reqs_in_progress == 0))) { /* Skip all requests that have been written completely but we're still waiting for a response. */ @@ -174,7 +175,7 @@ apr_status_t serf__conn_update_pollset(s desc.reqevents |= APR_POLLIN; /* Don't write if OpenSSL told us that it needs to read data first. */ - if (conn->stop_writing != 1) { + if (! conn->stop_writing) { /* If the connection is not closing down and * has unwritten data or @@ -201,7 +202,9 @@ apr_status_t serf__conn_update_pollset(s } /* If we can have async responses, always look for something to read. */ - if (conn->async_responses) { + if (conn->framing_type != SERF_CONNECTION_FRAMING_TYPE_HTTP1 + || conn->async_responses) + { desc.reqevents |= APR_POLLIN; } @@ -719,6 +722,7 @@ static apr_status_t reset_connection(ser conn->connect_time = 0; conn->latency = -1; conn->stop_writing = 0; + conn->write_now = 0; /* conn->pipelining */ conn->status = APR_SUCCESS; @@ -1274,8 +1278,17 @@ static apr_status_t read_from_connection } /* Unexpected response from the server */ + if (conn->write_now) { + status = write_to_connection(conn); + + if (!SERF_BUCKET_READ_ERROR(status)) + status = APR_SUCCESS; + } } + if (conn->framing_type == SERF_CONNECTION_FRAMING_TYPE_NONE) + break; + /* If the request doesn't have a response bucket, then call the * acceptor to get one created. */ @@ -1552,7 +1565,10 @@ serf_connection_t *serf_connection_creat conn->hit_eof = 0; conn->state = SERF_CONN_INIT; conn->latency = -1; /* unknown */ + conn->stop_writing = 0; + conn->write_now = 0; conn->pipelining = 1; + conn->framing_type = SERF_CONNECTION_FRAMING_TYPE_HTTP1; /* Create a subpool for our connection. */ apr_pool_create(&conn->skt_pool, conn->pool); @@ -1736,6 +1752,21 @@ void serf_connection_set_async_responses conn->async_handler_baton = handler_baton; } +void serf_connection_set_framing_type( + serf_connection_t *conn, + serf_connection_framing_type_t framing_type) +{ + conn->framing_type = framing_type; + + if (conn->skt) { + conn->dirty_conn = 1; + conn->ctx->dirty_pollset = 1; + conn->stop_writing = 0; + conn->write_now = 1; + serf__conn_update_pollset(conn); + } +} + static serf_request_t * create_request(serf_connection_t *conn, serf_request_setup_t setup, Modified: serf/trunk/serf.h URL: http://svn.apache.org/viewvc/serf/trunk/serf.h?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/serf.h (original) +++ serf/trunk/serf.h Thu Oct 15 21:52:17 2015 @@ -539,6 +539,24 @@ void serf_connection_set_async_responses serf_response_handler_t handler, void *handler_baton); +typedef enum serf_connection_framing_type_t { + SERF_CONNECTION_FRAMING_TYPE_NONE = 0, + SERF_CONNECTION_FRAMING_TYPE_HTTP1, + SERF_CONNECTION_FRAMING_TYPE_HTTP2 +} serf_connection_framing_type_t; + +/** +* Sets the connection framing on the connection to the specified type. The +* NONE type specifies that the framing type is undetermined yet and no +* requests should be written to the connection until the framing type is +* set. Connections default to HTTP1 framing. +* +* @since New in 1.4. +*/ +void serf_connection_set_framing_type( + serf_connection_t *conn, + serf_connection_framing_type_t framing_type); + /** * Setup the @a request for delivery on its connection. * Modified: serf/trunk/serf_private.h URL: http://svn.apache.org/viewvc/serf/trunk/serf_private.h?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/serf_private.h (original) +++ serf/trunk/serf_private.h Thu Oct 15 21:52:17 2015 @@ -377,6 +377,9 @@ struct serf_connection_t { /* Max. number of outstanding requests. */ unsigned int max_outstanding_requests; + /* Framing type to use on the connection */ + serf_connection_framing_type_t framing_type; + /* Flag to enable or disable HTTP pipelining. This flag is used internally only. */ int pipelining; @@ -402,6 +405,9 @@ struct serf_connection_t { /* Needs to read first before we can write again. */ int stop_writing; + /* Write out information now */ + int write_now; + /* Configuration shared with buckets and authn plugins */ serf_config_t *config; }; Modified: serf/trunk/test/MockHTTPinC/MockHTTP.h URL: http://svn.apache.org/viewvc/serf/trunk/test/MockHTTPinC/MockHTTP.h?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/test/MockHTTPinC/MockHTTP.h (original) +++ serf/trunk/test/MockHTTPinC/MockHTTP.h Thu Oct 15 21:52:17 2015 @@ -152,6 +152,10 @@ typedef struct mhResponseBldr_t mhRespon #define WithPort(port)\ mhSetServerPort(__servctx, port) +/* Specify which protocol the server should choose */ +#define WithProtocol(protocol)\ + mhSetServerProtocol(__servctx, protocol) + /* Set the maximum number of requests per connection. Default is unlimited */ #define WithMaxKeepAliveRequests(maxRequests)\ mhSetServerMaxRequestsPerConn(__servctx, maxRequests) @@ -602,6 +606,7 @@ void mhStartServer(mhServCtx_t *ctx); void mhStopServer(mhServCtx_t *ctx); mhServerSetupBldr_t *mhSetServerID(mhServCtx_t *ctx, const char *serverID); mhServerSetupBldr_t *mhSetServerPort(mhServCtx_t *ctx, unsigned int port); +mhServerSetupBldr_t *mhSetServerProtocol(mhServCtx_t *ctx, const char *protocols); mhServerSetupBldr_t *mhSetServerType(mhServCtx_t *ctx, mhServerType_t type); mhServerSetupBldr_t *mhSetServerThreading(mhServCtx_t *ctx, mhThreading_t threading); Modified: serf/trunk/test/MockHTTPinC/MockHTTP_private.h URL: http://svn.apache.org/viewvc/serf/trunk/test/MockHTTPinC/MockHTTP_private.h?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/test/MockHTTPinC/MockHTTP_private.h (original) +++ serf/trunk/test/MockHTTPinC/MockHTTP_private.h Thu Oct 15 21:52:17 2015 @@ -113,6 +113,7 @@ struct mhServCtx_t { const char *hostname; const char *serverID; /* unique id for this server */ apr_port_t port; + const char *alpn; apr_pollset_t *pollset; apr_socket_t *skt; /* Server listening socket */ mhServerType_t type; Modified: serf/trunk/test/MockHTTPinC/MockHTTP_server.c URL: http://svn.apache.org/viewvc/serf/trunk/test/MockHTTPinC/MockHTTP_server.c?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/test/MockHTTPinC/MockHTTP_server.c (original) +++ serf/trunk/test/MockHTTPinC/MockHTTP_server.c Thu Oct 15 21:52:17 2015 @@ -1867,6 +1867,21 @@ mhServerSetupBldr_t *mhSetServerPort(mhS return ssb; } +static bool set_server_protocol(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx) +{ + ctx->alpn = ssb->ibaton; + return YES; +} + +mhServerSetupBldr_t *mhSetServerProtocol(mhServCtx_t *ctx, const char *protocols) +{ + apr_pool_t *pool = ctx->pool; + mhServerSetupBldr_t *ssb = createServerSetupBldr(pool); + ssb->ibaton = apr_pstrdup(pool, protocols); + ssb->serversetup = set_server_protocol; + return ssb; +} + /** * Builder callback, sets the server type on server CTX. */ @@ -2499,6 +2514,21 @@ static apr_status_t initSSL(_mhClientCtx return APR_SUCCESS; } +static int alpn_select_callback(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + const char *select = arg; + + *out = select; + *outlen = strlen(select); + + return SSL_TLSEXT_ERR_OK; +} + /** * Inits the OpenSSL context. */ @@ -2543,6 +2573,14 @@ static apr_status_t initSSLCtx(_mhClient SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_TLSv1_2); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10002000L /* >= 1.0.2 */ + if (cctx->serv_ctx->alpn) { + SSL_CTX_set_alpn_select_cb(ssl_ctx->ctx, + alpn_select_callback, + cctx->serv_ctx->alpn); + } +#endif + if (cctx->protocols == mhProtoSSLv2) { /* In recent versions of OpenSSL, SSLv2 has been disabled by removing all SSLv2 ciphers from the cipher string. Modified: serf/trunk/test/serf_get.c URL: http://svn.apache.org/viewvc/serf/trunk/test/serf_get.c?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/test/serf_get.c (original) +++ serf/trunk/test/serf_get.c Thu Oct 15 21:52:17 2015 @@ -37,6 +37,7 @@ typedef struct app_baton_t { const char *hostinfo; int using_ssl; int head_request; + int negotiate_http2; const char *pem_path; const char *pem_pwd; serf_bucket_alloc_t *bkt_alloc; @@ -46,6 +47,7 @@ typedef struct app_baton_t { typedef struct conn_baton_t { app_baton_t *app; serf_ssl_context_t *ssl_ctx; + serf_connection_t *conn; } conn_baton_t; static void closed_connection(serf_connection_t *conn, @@ -181,11 +183,30 @@ static apr_status_t print_certs(void *da return APR_SUCCESS; } +/* Implements serf_ssl_protocol_result_cb_t for conn_setup */ +static apr_status_t conn_set_protocol(void *baton, + const char *protocol) +{ + conn_baton_t *conn_ctx = baton; + + if (!strcmp(protocol, "h2")) { + serf_connection_set_framing_type( + conn_ctx->conn, + SERF_CONNECTION_FRAMING_TYPE_HTTP2); + } else /* "http/1.1" or "" */ { + serf_connection_set_framing_type( + conn_ctx->conn, + SERF_CONNECTION_FRAMING_TYPE_HTTP1); + } + + return APR_SUCCESS; +} + static apr_status_t conn_setup(apr_socket_t *skt, - serf_bucket_t **input_bkt, - serf_bucket_t **output_bkt, - void *setup_baton, - apr_pool_t *pool) + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) { serf_bucket_t *c; conn_baton_t *conn_ctx = setup_baton; @@ -219,6 +240,20 @@ static apr_status_t conn_setup(apr_socke ctx, pool); } + + if (ctx->negotiate_http2) { + if (!serf_ssl_negotiate_protocol(conn_ctx->ssl_ctx, + "h2,http/1.1", + conn_set_protocol, conn_ctx)) + { + serf_bucket_t *bkt; + + /* Disable sending initial data until negotiate is done */ + serf_connection_set_framing_type( + conn_ctx->conn, + SERF_CONNECTION_FRAMING_TYPE_NONE); + } + } } *input_bkt = c; @@ -449,6 +484,7 @@ credentials_callback(char **username, /* Value for 'no short code' should be > 255 */ #define CERTFILE 256 #define CERTPWD 257 +#define HTTP2FLAG 258 static const apr_getopt_option_t options[] = { @@ -468,6 +504,9 @@ static const apr_getopt_option_t options {"certpwd", CERTPWD, 1, "<password> Password for the SSL client certificate"}, {NULL, 'r', 1, "<header:value> Use <header:value> as request header"}, {"debug", 'd', 0, "Enable debugging"}, +#if 0 + {"http2", HTTP2FLAG, 0, "Enable http2 (https only)"} +#endif }; static void print_usage(apr_pool_t *pool) @@ -510,7 +549,7 @@ int main(int argc, const char **argv) const char *raw_url, *method, *req_body_path = NULL; int count, inflight, conn_count; int i; - int print_headers, debug; + int print_headers, debug, negotiate_http2; const char *username = NULL; const char *password = ""; const char *pem_path = NULL, *pem_pwd = NULL; @@ -535,8 +574,8 @@ int main(int argc, const char **argv) print_headers = 0; /* Do not debug by default. */ debug = 0; + negotiate_http2 = 0; - apr_getopt_init(&opt, pool, argc, argv); while ((status = apr_getopt_long(opt, options, &opt_c, &opt_arg)) == APR_SUCCESS) { @@ -628,6 +667,9 @@ int main(int argc, const char **argv) case CERTPWD: pem_pwd = opt_arg; break; + case HTTP2FLAG: + negotiate_http2 = 1; + break; case 'v': puts("Serf version: " SERF_VERSION_STRING); exit(0); @@ -653,9 +695,12 @@ int main(int argc, const char **argv) if (strcasecmp(url.scheme, "https") == 0) { app_ctx.using_ssl = 1; + + app_ctx.negotiate_http2 = negotiate_http2; } else { app_ctx.using_ssl = 0; + app_ctx.negotiate_http2 = FALSE; } if (strcasecmp(method, "HEAD") == 0) { @@ -763,6 +808,8 @@ int main(int argc, const char **argv) exit(1); } + conn_ctx->conn = connections[i]; + serf_connection_set_max_outstanding_requests(connections[i], inflight); } Modified: serf/trunk/test/test_ssl.c URL: http://svn.apache.org/viewvc/serf/trunk/test/test_ssl.c?rev=1708898&r1=1708897&r2=1708898&view=diff ============================================================================== --- serf/trunk/test/test_ssl.c (original) +++ serf/trunk/test/test_ssl.c Thu Oct 15 21:52:17 2015 @@ -31,7 +31,14 @@ #if defined(WIN32) && defined(_DEBUG) /* Include this file to allow running a Debug build of serf with a Release build of OpenSSL. */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +#pragma warning(push) +#pragma warning(disable: 4152) +#endif #include <openssl/applink.c> +#if defined(_MSC_VER) && _MSC_VER >= 1400 +#pragma warning(pop) +#endif #endif /* Test setting up the openssl library. */ @@ -2110,6 +2117,95 @@ static void test_ssl_server_cert_with_sa CuAssertTrue(tc, tb->result_flags & TEST_RESULT_SERVERCERTCB_CALLED); } +static apr_status_t http11_select_protocol(void *baton, + const char *protocol) +{ + test_baton_t *tb = baton; + + if (! strcmp(protocol, "http/1.1")) + serf_connection_set_framing_type(tb->connection, + SERF_CONNECTION_FRAMING_TYPE_HTTP1); + else + return APR_EGENERAL; /* Failure */ + + return APR_SUCCESS; +} + +static apr_status_t http11_alpn_setup(apr_socket_t *skt, + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) +{ + test_baton_t *tb = setup_baton; + apr_status_t status; + + status = default_https_conn_setup(skt, input_bkt, output_bkt, + setup_baton, pool); + if (status) + return status; + + status = serf_ssl_negotiate_protocol(tb->ssl_context, "h2,http/1.1", + http11_select_protocol, tb); + + if (!status) { + /* Delay writing out the protocol type until we know how */ + serf_connection_set_framing_type(tb->connection, + SERF_CONNECTION_FRAMING_TYPE_NONE); + } + + return APR_SUCCESS; +} + + +static void test_ssl_alpn_negotiate(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + handler_baton_t handler_ctx[1]; + const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); + int expected_failures; + apr_status_t status; + static const char *server_cert[] = { "serfservercert.pem", + NULL }; + + /* Set up a test context and a https server */ + tb->mh = mhInit(); + + InitMockServers(tb->mh) + SetupServer(WithHTTPS, WithID("server"), WithPort(30080), + WithProtocol("http/1.1Q"), + WithCertificateFilesPrefix(get_srcdir_file(tb->pool, + "test/certs")), + WithCertificateKeyFile(server_key), + WithCertificateKeyPassPhrase("serftest"), + WithCertificateFileArray(server_cert)) + EndInit + + tb->serv_port = mhServerPortNr(tb->mh); + tb->serv_host = apr_psprintf(tb->pool, "%s:%d", "localhost", tb->serv_port); + tb->serv_url = apr_psprintf(tb->pool, "https://%s", tb->serv_host); + + status = setup_test_client_https_context(tb, + http11_alpn_setup, + ssl_server_cert_cb_expect_failures, + tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + expected_failures = SERF_SSL_CERT_UNKNOWNCA; + tb->user_baton = &expected_failures; + + Given(tb->mh) + GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"), + HeaderEqualTo("Host", tb->serv_host)) + Respond(WithCode(200), WithChunkedBody("")) + EndGiven + + create_new_request(tb, &handler_ctx[0], "GET", "/", 1); + + run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, + handler_ctx, tb->pool); +} + CuSuite *test_ssl(void) { CuSuite *suite = CuSuiteNew(); @@ -2154,6 +2250,7 @@ CuSuite *test_ssl(void) SUITE_ADD_TEST(suite, test_ssl_server_cert_with_cnsan_nul_byte); SUITE_ADD_TEST(suite, test_ssl_server_cert_with_san_and_empty_cb); SUITE_ADD_TEST(suite, test_ssl_renegotiate); + SUITE_ADD_TEST(suite, test_ssl_alpn_negotiate); return suite; }