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;
}