Return-Path: X-Original-To: apmail-httpd-cvs-archive@www.apache.org Delivered-To: apmail-httpd-cvs-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 56EA211983 for ; Thu, 1 May 2014 11:47:05 +0000 (UTC) Received: (qmail 28080 invoked by uid 500); 1 May 2014 11:46:20 -0000 Delivered-To: apmail-httpd-cvs-archive@httpd.apache.org Received: (qmail 27817 invoked by uid 500); 1 May 2014 11:46:04 -0000 Mailing-List: contact cvs-help@httpd.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@httpd.apache.org list-help: list-unsubscribe: List-Post: List-Id: Delivered-To: mailing list cvs@httpd.apache.org Received: (qmail 27388 invoked by uid 99); 1 May 2014 11:45:43 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 May 2014 11:45:43 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 01 May 2014 11:45:39 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id B40D62388C9A; Thu, 1 May 2014 11:44:05 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1591622 [31/33] - in /httpd/mod_spdy/trunk: ./ base/ base/base.xcodeproj/ base/metrics/ build/ build/all.xcodeproj/ build/build_util.xcodeproj/ build/install.xcodeproj/ build/internal/ build/linux/ build/mac/ build/util/ build/win/ install... Date: Thu, 01 May 2014 11:43:45 -0000 To: cvs@httpd.apache.org From: jim@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140501114405.B40D62388C9A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: httpd/mod_spdy/trunk/scripts/loadtest.py URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/scripts/loadtest.py?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/scripts/loadtest.py (added) +++ httpd/mod_spdy/trunk/scripts/loadtest.py Thu May 1 11:43:36 2014 @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +# Copyright 2012 Google Inc. +# +# Licensed 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. + +# A simple script for load-testing mod_spdy (or any other SPDY server). For +# example, to hit the server with 150 simultaneous SPDY clients, each fetching +# the URLs https://example.com/ and https://example.com/image.jpg, you would +# run: +# +# $ ./loadtest.py spdy 150 https://example.com/ https://example.com/image.jpg +# +# To run the same test with plain HTTPS clients instead of SPDY clients (for +# comparison), you would run: +# +# $ ./loadtest.py https 150 https://example.com/ https://example.com/image.jpg +# +# Press Ctrl-C to stop the test. +# +# You must have spdycat (https://github.com/tatsuhiro-t/spdylay) installed and +# on your $PATH in order to run SPDY tests, and you must have curl installed in +# order to run HTTPS or HTTP tests. + +from __future__ import division # Always convert ints to floats for / operator +from __future__ import print_function # Treat print as function, not keyword + +import re +import subprocess +import sys +import time + +#=============================================================================# + +def print_usage_and_quit(): + sys.stderr.write('Usage: {0} TYPE MAX_CLIENTS URL...\n'.format(sys.argv[0])) + sys.stderr.write('TYPE must be one of "spdy", "https", or "http"\n') + sys.stderr.write('MAX_CLIENTS must be a positive integer\n') + sys.exit(1) + +def with_scheme(url, scheme): + """Given a URL string, return a new URL string with the given scheme.""" + if re.match(r'^[a-zA-Z0-9]+:', url): + return re.sub(r'^[a-zA-Z0-9]+:', scheme + ':', url) + elif url.startswith('//'): + return scheme + ':' + url + else: + return scheme + '://' + url + + +class ClientProcess (object): + """A client subprocess that will try to load the URLs from the server.""" + + def __init__(self, key, command, factory): + self.__key = key + self.__child = subprocess.Popen(command, stdout=open('/dev/null', 'wb')) + self.__start_time = time.time() + self.__factory = factory + + def get_key(self): + return self.__key + + def get_start_time(self): + return self.__start_time + + def check_done(self): + """If the client is done, print time and return True, else return False.""" + code = self.__child.poll() + if code is None: + return False + else: + duration = time.time() - self.__start_time + self.__factory._client_finished(self.__key, code, duration) + return True + + def kill(self): + """Shut down this client.""" + self.__child.kill() + + +class ClientFactory (object): + """A factory for ClientProcess objects, that also tracks stats.""" + + def __init__(self, command): + """Create a factory that will use the given command for subprocesses.""" + self.__command = command + self.num_started = 0 + self.num_finished = 0 + self.max_duration = 0.0 + self.total_duration = 0.0 + + def new_client(self): + """Create and return a new ClientProcess.""" + self.num_started += 1 + return ClientProcess(key=self.num_started, command=self.__command, + factory=self) + + def _client_finished(self, key, code, duration): + """Called by each ClientProcess when it finishes.""" + self.num_finished += 1 + self.max_duration = max(self.max_duration, duration) + self.total_duration += duration + print('Client {0} exit {1} after {2:.3f}s'.format(key, code, duration)) + +#=============================================================================# + +if len(sys.argv) < 4: + print_usage_and_quit() + +# Determine what type of test we're doing and what URL scheme to use. +TYPE = sys.argv[1].lower() +if TYPE not in ['spdy', 'https', 'http']: + print_usage_and_quit() +SCHEME = 'https' if TYPE == 'spdy' else TYPE + +# Determine how many clients to have at once. +try: + MAX_CLIENTS = int(sys.argv[2]) +except ValueError: + print_usage_and_quit() +if MAX_CLIENTS < 1: + print_usage_and_quit() + +# Collect the URLs to fetch from. +URLS = [] +for url in sys.argv[3:]: + URLS.append(with_scheme(url, SCHEME)) + +# Put together the subprocess command to issue for each client. +if TYPE == 'spdy': + # The -n flag tells spdycat throw away the downloaded data without saving it. + COMMAND = ['spdycat', '-n'] + URLS +else: + # The -s flag tells curl to be silent (don't display progress meter); the -k + # flag tells curl to ignore certificate errors (e.g. self-signed certs). + COMMAND = ['curl', '-sk'] + URLS + +# Print out a summary of the test we'll be doing before we start. +print('TYPE={0}'.format(TYPE)) +print('URLS ({0}):'.format(len(URLS))) +for url in URLS: + print(' ' + url) +print('MAX_CLIENTS={0}'.format(MAX_CLIENTS)) + +# Run the test. +factory = ClientFactory(COMMAND) +clients = [] +try: + # Start us off with an initial batch of clients. + for index in xrange(MAX_CLIENTS): + clients.append(factory.new_client()) + # Each time a client finishes, replace it with a new client. + # TODO(mdsteele): This is a busy loop, which isn't great. What we want is to + # sleep until one or more children are done. Maybe we could do something + # clever that would allow us to do a select() call here or something. + while True: + for index in xrange(MAX_CLIENTS): + if clients[index].check_done(): + clients[index] = factory.new_client() +# Stop when the user hits Ctrl-C, and print a summary of the results. +except KeyboardInterrupt: + print() + if clients: + slowpoke = min(clients, key=(lambda c: c.get_key())) + print('Earliest unfinished client, {0}, not done after {1:.3f}s'.format( + slowpoke.get_key(), time.time() - slowpoke.get_start_time())) + if factory.num_finished > 0: + print('Avg time per client: {0:.3f}s ({1} started, {2} completed)'.format( + factory.total_duration / factory.num_finished, + factory.num_started, factory.num_finished)) + print('Max time per client: {0:.3f}s'.format(factory.max_duration)) + print("URLs served per second: {0:.3f}".format( + factory.num_finished * len(URLS) / factory.total_duration)) +for client in clients: + client.kill() + +#=============================================================================# Propchange: httpd/mod_spdy/trunk/scripts/loadtest.py ------------------------------------------------------------------------------ svn:eol-style = native Added: httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch (added) +++ httpd/mod_spdy/trunk/scripts/mod_ssl_with_npn.patch Thu May 1 11:43:36 2014 @@ -0,0 +1,231 @@ +Index: modules/ssl/ssl_engine_init.c +=================================================================== +--- modules/ssl/ssl_engine_init.c (revision 1367982) ++++ modules/ssl/ssl_engine_init.c (working copy) +@@ -559,6 +559,11 @@ + SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH); + + SSL_CTX_set_info_callback(ctx, ssl_callback_Info); ++ ++#ifdef HAVE_TLS_NPN ++ SSL_CTX_set_next_protos_advertised_cb( ++ ctx, ssl_callback_AdvertiseNextProtos, NULL); ++#endif + } + + static void ssl_init_ctx_verify(server_rec *s, +Index: modules/ssl/ssl_engine_io.c +=================================================================== +--- modules/ssl/ssl_engine_io.c (revision 1367982) ++++ modules/ssl/ssl_engine_io.c (working copy) +@@ -338,6 +338,7 @@ + apr_pool_t *pool; + char buffer[AP_IOBUFSIZE]; + ssl_filter_ctx_t *filter_ctx; ++ int npn_finished; /* 1 if NPN has finished, 0 otherwise */ + } bio_filter_in_ctx_t; + + /* +@@ -1409,6 +1410,27 @@ + APR_BRIGADE_INSERT_TAIL(bb, bucket); + } + ++#ifdef HAVE_TLS_NPN ++ /* By this point, Next Protocol Negotiation (NPN) should be completed (if ++ * our version of OpenSSL supports it). If we haven't already, find out ++ * which protocol was decided upon and inform other modules by calling ++ * npn_proto_negotiated_hook. */ ++ if (!inctx->npn_finished) { ++ const unsigned char *next_proto = NULL; ++ unsigned next_proto_len = 0; ++ ++ SSL_get0_next_proto_negotiated( ++ inctx->ssl, &next_proto, &next_proto_len); ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c, ++ "SSL NPN negotiated protocol: '%s'", ++ apr_pstrmemdup(f->c->pool, (const char*)next_proto, ++ next_proto_len)); ++ modssl_run_npn_proto_negotiated_hook( ++ f->c, (const char*)next_proto, next_proto_len); ++ inctx->npn_finished = 1; ++ } ++#endif ++ + return APR_SUCCESS; + } + +@@ -1753,6 +1775,7 @@ + inctx->block = APR_BLOCK_READ; + inctx->pool = c->pool; + inctx->filter_ctx = filter_ctx; ++ inctx->npn_finished = 0; + } + + void ssl_io_filter_init(conn_rec *c, SSL *ssl) +Index: modules/ssl/ssl_engine_kernel.c +=================================================================== +--- modules/ssl/ssl_engine_kernel.c (revision 1367982) ++++ modules/ssl/ssl_engine_kernel.c (working copy) +@@ -2139,3 +2139,84 @@ + } + + #endif /* HAVE_SRP */ ++ ++#ifdef HAVE_TLS_NPN ++/* ++ * This callback function is executed when SSL needs to decide what protocols ++ * to advertise during Next Protocol Negotiation (NPN). It must produce a ++ * string in wire format -- a sequence of length-prefixed strings -- indicating ++ * the advertised protocols. Refer to SSL_CTX_set_next_protos_advertised_cb ++ * in OpenSSL for reference. ++ */ ++int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data_out, ++ unsigned int *size_out, void *arg) ++{ ++ conn_rec *c = (conn_rec*)SSL_get_app_data(ssl); ++ apr_array_header_t *protos; ++ int num_protos; ++ unsigned int size; ++ int i; ++ unsigned char *data; ++ unsigned char *start; ++ ++ *data_out = NULL; ++ *size_out = 0; ++ ++ /* If the connection object is not available, then there's nothing for us ++ * to do. */ ++ if (c == NULL) { ++ return SSL_TLSEXT_ERR_OK; ++ } ++ ++ /* Invoke our npn_advertise_protos hook, giving other modules a chance to ++ * add alternate protocol names to advertise. */ ++ protos = apr_array_make(c->pool, 0, sizeof(char*)); ++ modssl_run_npn_advertise_protos_hook(c, protos); ++ num_protos = protos->nelts; ++ ++ /* We now have a list of null-terminated strings; we need to concatenate ++ * them together into a single string, where each protocol name is prefixed ++ * by its length. First, calculate how long that string will be. */ ++ size = 0; ++ for (i = 0; i < num_protos; ++i) { ++ const char *string = APR_ARRAY_IDX(protos, i, const char*); ++ unsigned int length = strlen(string); ++ /* If the protocol name is too long (the length must fit in one byte), ++ * then log an error and skip it. */ ++ if (length > 255) { ++ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, ++ "SSL NPN protocol name too long (length=%u): %s", ++ length, string); ++ continue; ++ } ++ /* Leave room for the length prefix (one byte) plus the protocol name ++ * itself. */ ++ size += 1 + length; ++ } ++ ++ /* If there is nothing to advertise (either because no modules added ++ * anything to the protos array, or because all strings added to the array ++ * were skipped), then we're done. */ ++ if (size == 0) { ++ return SSL_TLSEXT_ERR_OK; ++ } ++ ++ /* Now we can build the string. Copy each protocol name string into the ++ * larger string, prefixed by its length. */ ++ data = apr_palloc(c->pool, size * sizeof(unsigned char)); ++ start = data; ++ for (i = 0; i < num_protos; ++i) { ++ const char *string = APR_ARRAY_IDX(protos, i, const char*); ++ apr_size_t length = strlen(string); ++ *start = (unsigned char)length; ++ ++start; ++ memcpy(start, string, length * sizeof(unsigned char)); ++ start += length; ++ } ++ ++ /* Success. */ ++ *data_out = data; ++ *size_out = size; ++ return SSL_TLSEXT_ERR_OK; ++} ++#endif +Index: modules/ssl/mod_ssl.c +=================================================================== +--- modules/ssl/mod_ssl.c (revision 1367982) ++++ modules/ssl/mod_ssl.c (working copy) +@@ -220,6 +220,18 @@ + AP_END_CMD + }; + ++/* Implement 'modssl_run_npn_advertise_protos_hook'. */ ++APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL( ++ modssl, AP, int, npn_advertise_protos_hook, ++ (conn_rec *connection, apr_array_header_t *protos), ++ (connection, protos), OK, DECLINED); ++ ++/* Implement 'modssl_run_npn_proto_negotiated_hook'. */ ++APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL( ++ modssl, AP, int, npn_proto_negotiated_hook, ++ (conn_rec *connection, const char *proto_name, apr_size_t proto_name_len), ++ (connection, proto_name, proto_name_len), OK, DECLINED); ++ + /* + * the various processing hooks + */ +Index: modules/ssl/mod_ssl.h +=================================================================== +--- modules/ssl/mod_ssl.h (revision 1367982) ++++ modules/ssl/mod_ssl.h (working copy) +@@ -60,5 +60,26 @@ + + APR_DECLARE_OPTIONAL_FN(apr_array_header_t *, ssl_extlist_by_oid, (request_rec *r, const char *oidstr)); + ++/** The npn_advertise_protos optional hook allows other modules to add entries ++ * to the list of protocol names advertised by the server during the Next ++ * Protocol Negotiation (NPN) portion of the SSL handshake. The hook callee is ++ * given the connection and an APR array; it should push one or more char*'s ++ * pointing to null-terminated strings (such as "http/1.1" or "spdy/2") onto ++ * the array and return OK, or do nothing and return DECLINED. */ ++APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_advertise_protos_hook, ++ (conn_rec *connection, apr_array_header_t *protos)); ++ ++/** The npn_proto_negotiated optional hook allows other modules to discover the ++ * name of the protocol that was chosen during the Next Protocol Negotiation ++ * (NPN) portion of the SSL handshake. Note that this may be the empty string ++ * (in which case modules should probably assume HTTP), or it may be a protocol ++ * that was never even advertised by the server. The hook callee is given the ++ * connection, a non-null-terminated string containing the protocol name, and ++ * the length of the string; it should do something appropriate (i.e. insert or ++ * remove filters) and return OK, or do nothing and return DECLINED. */ ++APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_proto_negotiated_hook, ++ (conn_rec *connection, const char *proto_name, ++ apr_size_t proto_name_len)); ++ + #endif /* __MOD_SSL_H__ */ + /** @} */ +Index: modules/ssl/ssl_private.h +=================================================================== +--- modules/ssl/ssl_private.h (revision 1367982) ++++ modules/ssl/ssl_private.h (working copy) +@@ -146,6 +146,11 @@ + #define HAVE_ECC + #endif + ++#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_NEXTPROTONEG) \ ++ && !defined(OPENSSL_NO_TLSEXT) ++#define HAVE_TLS_NPN ++#endif ++ + /* OCSP stapling */ + #if !defined(OPENSSL_NO_OCSP) && defined(SSL_CTX_set_tlsext_status_cb) + #define HAVE_OCSP_STAPLING +@@ -614,6 +619,7 @@ + unsigned char aes_key[16]; + } modssl_ticket_key_t; + #endif ++int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg); + + typedef struct SSLSrvConfigRec SSLSrvConfigRec; + Added: httpd/mod_spdy/trunk/testing/OWNERS URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/testing/OWNERS?rev=1591622&view=auto ============================================================================== --- httpd/mod_spdy/trunk/testing/OWNERS (added) +++ httpd/mod_spdy/trunk/testing/OWNERS Thu May 1 11:43:36 2014 @@ -0,0 +1 @@ +*