--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/apache_spdy_session_io.h"
+
+#include "apr_buckets.h"
+#include "http_log.h"
+#include "util_filter.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mod_spdy/apache/pool_util.h" // for AprStatusString
+#include "mod_spdy/common/protocol_util.h" // for FrameData
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace {
+
+// How many bytes to ask for at a time when pulling data from the connection
+// input filters. We use non-blocking reads, so we'll sometimes get less than
+// this.
+const apr_off_t kReadBytes = 4096;
+
+} // namespace
+
+ApacheSpdySessionIO::ApacheSpdySessionIO(conn_rec* connection)
+ : connection_(connection),
+ input_brigade_(apr_brigade_create(connection_->pool,
+ connection_->bucket_alloc)),
+ output_brigade_(apr_brigade_create(connection_->pool,
+ connection_->bucket_alloc)) {}
+
+ApacheSpdySessionIO::~ApacheSpdySessionIO() {}
+
+bool ApacheSpdySessionIO::IsConnectionAborted() {
+ return static_cast<bool>(connection_->aborted);
+}
+
+SpdySessionIO::ReadStatus ApacheSpdySessionIO::ProcessAvailableInput(
+ bool block, net::BufferedSpdyFramer* framer) {
+ const apr_read_type_e read_type = block ? APR_BLOCK_READ : APR_NONBLOCK_READ;
+
+ // Make sure the input brigade we're using is empty.
+ if (!APR_BRIGADE_EMPTY(input_brigade_)) {
+ LOG(DFATAL) << "input_brigade_ should be empty";
+ apr_brigade_cleanup(input_brigade_);
+ }
+
+ // Try to read some data into the brigade.
+ {
+ const apr_status_t status = ap_get_brigade(
+ connection_->input_filters, input_brigade_, AP_MODE_READBYTES,
+ read_type, kReadBytes);
+ if (status == APR_SUCCESS) {
+ // Success; we'll process the brigade below.
+ } else if (APR_STATUS_IS_EAGAIN(status)) {
+ // EAGAIN probably indicates that we did a non-blocking read and no data
+ // was available. So, just press on and process the brigade (it should
+ // be empty, but maybe there'll be metadata buckets or something). Most
+ // likely we'll end up returning READ_NO_DATA at the end of this method.
+ } else if (APR_STATUS_IS_TIMEUP(status)) {
+ // TIMEUP tends to occur for blocking reads, if some upstream filter set
+ // a timeout. Just like with EAGAIN, we'll press on and process the
+ // probably-empty brigade, but since these seem to be rare, let's VLOG
+ // here so that we can see when they happen.
+ VLOG(3) << "ap_get_brigade returned TIMEUP";
+ } else {
+ // Otherwise, something has gone wrong and we should consider the
+ // connection closed. If the client merely closed the connection on us,
+ // we'll get an EOF error, which is fine; otherwise, something may be
+ // wrong, so we should log an error.
+ if (APR_STATUS_IS_EOF(status)) {
+ VLOG(2) << "ap_get_brigade returned EOF";
+ } else {
+ LOG(ERROR) << "ap_get_brigade failed with status " << status << ": "
+ << AprStatusString(status);
+ }
+ apr_brigade_cleanup(input_brigade_);
+ return READ_CONNECTION_CLOSED;
+ }
+ }
+
+ bool pushed_any_data = false;
+ while (!APR_BRIGADE_EMPTY(input_brigade_)) {
+ apr_bucket* bucket = APR_BRIGADE_FIRST(input_brigade_);
+
+ if (APR_BUCKET_IS_METADATA(bucket)) {
+ // Metadata bucket. We don't care about EOS or FLUSH buckets here (or
+ // other, unknown metadata buckets), and there's no further filter to
+ // pass it to, so we just ignore it.
+ } else {
+ // Data bucket -- get ready to read.
+ const char* data = NULL;
+ apr_size_t data_length = 0;
+ const apr_status_t status = apr_bucket_read(bucket, &data, &data_length,
+ read_type);
+ if (status != APR_SUCCESS) {
+ // TODO(mdsteele): In what situations might apr_bucket_read fail here?
+ // These buckets are almost certainly coming from mod_ssl, which
+ // seems to only use transient buckets, for which apr_bucket_read
+ // will always succeed. However, in theory there could be another
+ // filter between us and mod_ssl, and in theory it could be sending
+ // us bucket types for which non-blocking reads can fail.
+ LOG(ERROR) << "apr_bucket_read failed with status " << status << ": "
+ << AprStatusString(status);
+ }
+
+ const size_t consumed = framer->ProcessInput(data, data_length);
+ // If the SpdyFramer encountered an error (i.e. the client sent us
+ // malformed data), then we can't recover.
+ if (framer->HasError()) {
+ apr_brigade_cleanup(input_brigade_);
+ return READ_ERROR;
+ }
+ // If there was no error, the framer will have consumed all the data.
+ // TODO(mdsteele): Is that true? I think it's true.
+ DCHECK(consumed == data_length);
+ pushed_any_data |= consumed > 0;
+ }
+
+ // Delete this bucket and move on to the next one.
+ apr_bucket_delete(bucket);
+ }
+
+ // We deleted buckets as we went, so the brigade should be empty now.
+ DCHECK(APR_BRIGADE_EMPTY(input_brigade_));
+
+ return pushed_any_data ? READ_SUCCESS : READ_NO_DATA;
+}
+
+SpdySessionIO::WriteStatus ApacheSpdySessionIO::SendFrameRaw(
+ const net::SpdySerializedFrame& frame) {
+ // Make sure the output brigade we're using is empty.
+ if (!APR_BRIGADE_EMPTY(output_brigade_)) {
+ LOG(DFATAL) << "output_brigade_ should be empty";
+ apr_brigade_cleanup(output_brigade_);
+ }
+
+ // Put the frame data into the output brigade.
+ APR_BRIGADE_INSERT_TAIL(output_brigade_, apr_bucket_transient_create(
+ frame.data(), frame.size(), output_brigade_->bucket_alloc));
+
+ // Append a flush bucket to the end of the brigade, to make sure that this
+ // frame makes it all the way out to the client.
+ APR_BRIGADE_INSERT_TAIL(output_brigade_, apr_bucket_flush_create(
+ output_brigade_->bucket_alloc));
+
+ // Send the brigade through the connection's output filter chain.
+ const apr_status_t status =
+ ap_pass_brigade(connection_->output_filters, output_brigade_);
+ apr_brigade_cleanup(output_brigade_);
+ DCHECK(APR_BRIGADE_EMPTY(output_brigade_));
+
+ // If we sent the data successfully, great; otherwise, consider the
+ // connection closed.
+ if (status == APR_SUCCESS) {
+ return WRITE_SUCCESS;
+ } else {
+ // ECONNABORTED and EPIPE (broken pipe) are two common symptoms of the
+ // connection having been closed; those are no cause for concern. For any
+ // other non-success status, log an error (for now).
+ if (APR_STATUS_IS_ECONNABORTED(status)) {
+ VLOG(2) << "ap_pass_brigade returned ECONNABORTED";
+ } else if (APR_STATUS_IS_EPIPE(status)) {
+ VLOG(2) << "ap_pass_brigade returned EPIPE";
+ } else {
+ LOG(ERROR) << "ap_pass_brigade failed with status " << status << ": "
+ << AprStatusString(status);
+ }
+ return WRITE_CONNECTION_CLOSED;
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_APACHE_APACHE_SPDY_SESSION_IO_H_
+#define MOD_SPDY_APACHE_APACHE_SPDY_SESSION_IO_H_
+
+#include "httpd.h"
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/spdy_session_io.h"
+
+namespace net {
+class BufferedSpdyFramer;
+class SpdyFrame;
+} // namespace net
+
+namespace mod_spdy {
+
+class ApacheSpdySessionIO : public SpdySessionIO {
+ public:
+ explicit ApacheSpdySessionIO(conn_rec* connection);
+ ~ApacheSpdySessionIO();
+
+ // SpdySessionIO methods:
+ virtual bool IsConnectionAborted();
+ virtual ReadStatus ProcessAvailableInput(bool block,
+ net::BufferedSpdyFramer* framer);
+ virtual WriteStatus SendFrameRaw(const net::SpdySerializedFrame& frame);
+
+ private:
+ conn_rec* const connection_;
+ apr_bucket_brigade* const input_brigade_;
+ apr_bucket_brigade* const output_brigade_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheSpdySessionIO);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_APACHE_SPDY_SESSION_IO_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/apache_spdy_stream_task_factory.h"
+
+#include "apr_buckets.h"
+#include "apr_network_io.h"
+#include "http_log.h"
+#include "util_filter.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mod_spdy/apache/config_util.h"
+#include "mod_spdy/apache/filters/http_to_spdy_filter.h"
+#include "mod_spdy/apache/filters/spdy_to_http_filter.h"
+#include "mod_spdy/apache/log_message_handler.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/apache/slave_connection.h"
+#include "mod_spdy/apache/slave_connection_context.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "net/instaweb/util/public/function.h"
+
+namespace mod_spdy {
+
+namespace {
+
+// These global variables store the filter handles for our filters. Normally,
+// global variables would be very dangerous in a concurrent environment like
+// Apache, but these ones are okay because they are assigned just once, at
+// start-up (during which Apache is running single-threaded; see TAMB 2.2.1),
+// and are read-only thereafter.
+ap_filter_rec_t* gHttpToSpdyFilterHandle = NULL;
+ap_filter_rec_t* gSpdyToHttpFilterHandle = NULL;
+
+// See TAMB 8.4.2
+apr_status_t SpdyToHttpFilterFunc(ap_filter_t* filter,
+ apr_bucket_brigade* brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes) {
+ mod_spdy::SpdyToHttpFilter* spdy_to_http_filter =
+ static_cast<mod_spdy::SpdyToHttpFilter*>(filter->ctx);
+ return spdy_to_http_filter->Read(filter, brigade, mode, block, readbytes);
+}
+
+// See TAMB 8.4.1
+apr_status_t HttpToSpdyFilterFunc(ap_filter_t* filter,
+ apr_bucket_brigade* input_brigade) {
+ mod_spdy::HttpToSpdyFilter* http_to_spdy_filter =
+ static_cast<mod_spdy::HttpToSpdyFilter*>(filter->ctx);
+ return http_to_spdy_filter->Write(filter, input_brigade);
+}
+
+// A task to be returned by ApacheSpdyStreamTaskFactory::NewStreamTask().
+class ApacheStreamTask : public net_instaweb::Function {
+ public:
+ // The task does not take ownership of the arguments.
+ ApacheStreamTask(SlaveConnectionFactory* conn_factory,
+ SpdyStream* stream);
+ virtual ~ApacheStreamTask();
+
+ protected:
+ // net_instaweb::Function methods:
+ virtual void Run();
+ virtual void Cancel();
+
+ private:
+ SpdyStream* const stream_;
+ scoped_ptr<SlaveConnection> slave_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheStreamTask);
+};
+
+ApacheStreamTask::ApacheStreamTask(SlaveConnectionFactory* conn_factory,
+ SpdyStream* stream)
+ : stream_(stream),
+ slave_connection_(conn_factory->Create()) {
+ const SpdyServerConfig* config =
+ GetServerConfig(slave_connection_->apache_connection());
+
+ // SlaveConnectionFactory::Create must have attached a slave context.
+ SlaveConnectionContext* slave_context =
+ slave_connection_->GetSlaveConnectionContext();
+ slave_context->set_slave_stream(stream);
+
+ // Create our filters to hook us up to the slave connection.
+ SpdyToHttpFilter* spdy_to_http_filter = new SpdyToHttpFilter(stream);
+ PoolRegisterDelete(slave_connection_->apache_connection()->pool,
+ spdy_to_http_filter);
+ slave_context->SetInputFilter(gSpdyToHttpFilterHandle, spdy_to_http_filter);
+
+ HttpToSpdyFilter* http_to_spdy_filter = new HttpToSpdyFilter(config, stream);
+ PoolRegisterDelete(slave_connection_->apache_connection()->pool,
+ http_to_spdy_filter);
+ slave_context->SetOutputFilter(gHttpToSpdyFilterHandle, http_to_spdy_filter);
+}
+
+ApacheStreamTask::~ApacheStreamTask() {
+}
+
+void ApacheStreamTask::Run() {
+ ScopedStreamLogHandler log_handler(
+ slave_connection_->apache_connection(), stream_);
+ VLOG(3) << "Starting stream task";
+ if (!stream_->is_aborted()) {
+ slave_connection_->Run();
+ }
+ VLOG(3) << "Finishing stream task";
+}
+
+void ApacheStreamTask::Cancel() {
+ if (VLOG_IS_ON(3)) {
+ ScopedStreamLogHandler log_handler(
+ slave_connection_->apache_connection(), stream_);
+ VLOG(3) << "Cancelling stream task";
+ }
+}
+
+} // namespace
+
+ApacheSpdyStreamTaskFactory::ApacheSpdyStreamTaskFactory(conn_rec* connection)
+ : connection_factory_(connection) {}
+
+ApacheSpdyStreamTaskFactory::~ApacheSpdyStreamTaskFactory() {}
+
+void ApacheSpdyStreamTaskFactory::InitFilters() {
+ // Register our input filter, and store the filter handle into a global
+ // variable so we can use it later to instantiate our filter into a filter
+ // chain. The "filter type" argument below determines where in the filter
+ // chain our filter will be placed. We use AP_FTYPE_NETWORK so that we will
+ // be at the very end of the input chain for slave connections, in place of
+ // the usual core input filter.
+ gSpdyToHttpFilterHandle = ap_register_input_filter(
+ "SPDY_TO_HTTP", // name
+ SpdyToHttpFilterFunc, // filter function
+ NULL, // init function (n/a in our case)
+ AP_FTYPE_NETWORK); // filter type
+
+ // Now register our output filter, analogously to the input filter above.
+ gHttpToSpdyFilterHandle = ap_register_output_filter(
+ "HTTP_TO_SPDY", // name
+ HttpToSpdyFilterFunc, // filter function
+ NULL, // init function (n/a in our case)
+ AP_FTYPE_NETWORK); // filter type
+}
+
+net_instaweb::Function* ApacheSpdyStreamTaskFactory::NewStreamTask(
+ SpdyStream* stream) {
+ return new ApacheStreamTask(&connection_factory_, stream);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_APACHE_APACHE_SPDY_STREAM_TASK_FACTORY_H_
+#define MOD_SPDY_APACHE_APACHE_SPDY_STREAM_TASK_FACTORY_H_
+
+#include "httpd.h"
+
+#include "base/basictypes.h"
+#include "mod_spdy/apache/slave_connection.h"
+#include "mod_spdy/common/spdy_stream_task_factory.h"
+
+namespace net_instaweb { class Function; }
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+class ApacheSpdyStreamTaskFactory : public SpdyStreamTaskFactory {
+ public:
+ explicit ApacheSpdyStreamTaskFactory(conn_rec* connection);
+ ~ApacheSpdyStreamTaskFactory();
+
+ // This must be called from hooks registration to create the filters
+ // this class needs to route bytes between Apache & mod_spdy.
+ static void InitFilters();
+
+ // SpdyStreamTaskFactory methods:
+ virtual net_instaweb::Function* NewStreamTask(SpdyStream* stream);
+
+ private:
+ SlaveConnectionFactory connection_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApacheSpdyStreamTaskFactory);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_APACHE_SPDY_STREAM_TASK_FACTORY_H_
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/apache/config_commands.h"
+
+#include "apr_strings.h"
+
+#include "base/strings/string_number_conversions.h"
+
+#include "mod_spdy/apache/config_util.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace mod_spdy {
+
+void* CreateSpdyServerConfig(apr_pool_t* pool, server_rec* server) {
+ SpdyServerConfig* config = new SpdyServerConfig;
+ PoolRegisterDelete(pool, config);
+ return config;
+}
+
+void* MergeSpdyServerConfigs(apr_pool_t* pool, void* base, void* add) {
+ SpdyServerConfig* config = new SpdyServerConfig;
+ PoolRegisterDelete(pool, config);
+ config->MergeFrom(*static_cast<SpdyServerConfig*>(base),
+ *static_cast<SpdyServerConfig*>(add));
+ return config;
+}
+
+namespace {
+
+// A function suitable for for passing to AP_INIT_TAKE1 (and hence to
+// SPDY_CONFIG_COMMAND) for a config option that requires a boolean argument
+// ("on" or "off", case-insensitive; other strings will be rejected). The
+// template argument is a setter method on SpdyServerConfig that takes a bool.
+template <void(SpdyServerConfig::*setter)(bool)>
+const char* SetBoolean(cmd_parms* cmd, void* dir, const char* arg) {
+ if (0 == apr_strnatcasecmp(arg, "on")) {
+ (GetServerConfig(cmd)->*setter)(true);
+ return NULL;
+ } else if (0 == apr_strnatcasecmp(arg, "off")) {
+ (GetServerConfig(cmd)->*setter)(false);
+ return NULL;
+ } else {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, " on|off", NULL);
+ }
+}
+
+// A function suitable for for passing to AP_INIT_TAKE1 (and hence to
+// SPDY_CONFIG_COMMAND) for a config option that requires a positive integer
+// argument. The template argument is a setter method on SpdyServerConfig that
+// takes an int; the method will only ever be called with a positive argument
+// (if the user gives a non-positive argument, or a string that isn't even an
+// integer, this function will reject it with an error message).
+template <void(SpdyServerConfig::*setter)(int)>
+const char* SetPositiveInt(cmd_parms* cmd, void* dir, const char* arg) {
+ int value;
+ if (!base::StringToInt(arg, &value) || value < 1) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " must specify a positive integer", NULL);
+ }
+ (GetServerConfig(cmd)->*setter)(value);
+ return NULL;
+}
+
+// Like SetPositiveInt, but allows any non-negative value, not just positive.
+template <void(SpdyServerConfig::*setter)(int)>
+const char* SetNonNegativeInt(cmd_parms* cmd, void* dir, const char* arg) {
+ int value;
+ if (!base::StringToInt(arg, &value) || value < 0) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " must specify a non-negative integer", NULL);
+ }
+ (GetServerConfig(cmd)->*setter)(value);
+ return NULL;
+}
+
+const char* SetUseSpdyForNonSslConnections(cmd_parms* cmd, void* dir,
+ const char* arg) {
+ spdy::SpdyVersion value;
+ if (0 == apr_strnatcasecmp(arg, "off")) {
+ value = spdy::SPDY_VERSION_NONE;
+ } else if (0 == apr_strnatcasecmp(arg, "2")) {
+ value = spdy::SPDY_VERSION_2;
+ } else if (0 == apr_strnatcasecmp(arg, "3")) {
+ value = spdy::SPDY_VERSION_3;
+ } else if (0 == apr_strnatcasecmp(arg, "3.1")) {
+ value = spdy::SPDY_VERSION_3_1;
+ } else {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " must be 2, 3, 3.1, or off", NULL);
+ }
+ GetServerConfig(cmd)->set_use_spdy_version_without_ssl(value);
+ return NULL;
+}
+
+// This template can be wrapped around any of the above functions to restrict
+// the directive to being used only at the top level (as opposed to within a
+// <VirtualHost> directive).
+template <const char*(*setter)(cmd_parms*, void*, const char*)>
+const char* GlobalOnly(cmd_parms* cmd, void* dir, const char* arg) {
+ const char* error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ return error != NULL ? error : (*setter)(cmd, dir, arg);
+}
+
+} // namespace
+
+// The reinterpret_cast is there because Apache's AP_INIT_TAKE1 macro needs to
+// take an old-style C function type with unspecified arguments. The
+// static_cast, then, is just to enforce that we pass the correct type of
+// function -- it will give a compile-time error if we pass a function with the
+// wrong signature.
+#define SPDY_CONFIG_COMMAND(name, fn, help) \
+ AP_INIT_TAKE1( \
+ name, \
+ reinterpret_cast<const char*(*)()>( \
+ static_cast<const char*(*)(cmd_parms*,void*,const char*)>(fn)), \
+ NULL, RSRC_CONF, help)
+
+const command_rec kSpdyConfigCommands[] = {
+ SPDY_CONFIG_COMMAND(
+ "SpdyEnabled", SetBoolean<&SpdyServerConfig::set_spdy_enabled>,
+ "Enable SPDY support"),
+ SPDY_CONFIG_COMMAND(
+ "SpdyMaxStreamsPerConnection",
+ SetPositiveInt<&SpdyServerConfig::set_max_streams_per_connection>,
+ "Maxiumum number of simultaneous SPDY streams per connection"),
+ SPDY_CONFIG_COMMAND(
+ "SpdyMinThreadsPerProcess",
+ GlobalOnly<SetPositiveInt<
+ &SpdyServerConfig::set_min_threads_per_process> >,
+ "Miniumum number of worker threads to spawn per child process"),
+ SPDY_CONFIG_COMMAND(
+ "SpdyMaxThreadsPerProcess",
+ GlobalOnly<SetPositiveInt<
+ &SpdyServerConfig::set_max_threads_per_process> >,
+ "Maximum number of worker threads to spawn per child process"),
+ SPDY_CONFIG_COMMAND(
+ "SpdyMaxServerPushDepth",
+ SetNonNegativeInt<
+ &SpdyServerConfig::set_max_server_push_depth>,
+ "Maximum number of recursive levels to follow X-Associated-Content header. 0 Disables. Defaults to 1."),
+ SPDY_CONFIG_COMMAND(
+ "SpdySendVersionHeader",
+ SetBoolean<&SpdyServerConfig::set_send_version_header>,
+ "Send an x-mod-spdy header with the module version number"),
+ // Debugging commands, which should not be used in production:
+ SPDY_CONFIG_COMMAND(
+ "SpdyDebugLoggingVerbosity",
+ GlobalOnly<SetNonNegativeInt<&SpdyServerConfig::set_vlog_level> >,
+ "Set the verbosity of mod_spdy logging"),
+ SPDY_CONFIG_COMMAND(
+ "SpdyDebugUseSpdyForNonSslConnections",
+ SetUseSpdyForNonSslConnections,
+ "Use SPDY even over non-SSL connections; DO NOT USE IN PRODUCTION"),
+ {NULL}
+};
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_APACHE_CONFIG_COMMANDS_H_
+#define MOD_SPDY_APACHE_CONFIG_COMMANDS_H_
+
+#include "httpd.h"
+#include "http_config.h"
+
+namespace mod_spdy {
+
+// An array of configuration command objects, to be placed into an Apache
+// module object. See TAMB 9.4.
+extern const command_rec kSpdyConfigCommands[];
+
+// A function to create new server config objects, with a function signature
+// appropriate to be placed into an Apache module object. See TAMB 9.3.1.
+void* CreateSpdyServerConfig(apr_pool_t* pool, server_rec* server);
+
+// A function to merge existing server config objects, with a signature
+// appropriate to be placed into an Apache module object. See TAMB 9.5.
+void* MergeSpdyServerConfigs(apr_pool_t* pool, void* base, void* add);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_CONFIG_COMMANDS_H_
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/apache/config_util.h"
+
+#include "httpd.h"
+#include "http_config.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+
+#include "mod_spdy/apache/master_connection_context.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/apache/slave_connection_context.h"
+#include "mod_spdy/common/spdy_server_config.h"
+
+extern "C" {
+ extern module AP_MODULE_DECLARE_DATA spdy_module;
+}
+
+namespace mod_spdy {
+
+namespace {
+
+struct ConnectionContext {
+ // Exactly one of the fields below should be set.
+ scoped_ptr<MasterConnectionContext> master_context;
+ scoped_ptr<SlaveConnectionContext> slave_context;
+};
+
+SpdyServerConfig* GetServerConfigInternal(server_rec* server) {
+ void* ptr = ap_get_module_config(server->module_config, &spdy_module);
+ CHECK(ptr) << "mod_spdy server config pointer is NULL";
+ return static_cast<SpdyServerConfig*>(ptr);
+}
+
+ConnectionContext* GetConnContextInternal(conn_rec* connection) {
+ return static_cast<ConnectionContext*>(
+ ap_get_module_config(connection->conn_config, &spdy_module));
+}
+
+ConnectionContext* SetConnContextInternal(
+ conn_rec* connection,
+ MasterConnectionContext* master_context,
+ SlaveConnectionContext* slave_context) {
+ DCHECK((master_context == NULL) ^ (slave_context == NULL));
+ DCHECK(GetConnContextInternal(connection) == NULL);
+ ConnectionContext* context = new ConnectionContext;
+ PoolRegisterDelete(connection->pool, context);
+ context->master_context.reset(master_context);
+ context->slave_context.reset(slave_context);
+
+ // Place the context object in the connection's configuration vector, so that
+ // other hook functions with access to this connection can get hold of the
+ // context object. See TAMB 4.2 for details.
+ ap_set_module_config(connection->conn_config, // configuration vector
+ &spdy_module, // module with which to associate
+ context); // pointer to store (any void* we want)
+
+ return context;
+}
+
+MasterConnectionContext* GetMasterConnectionContextInternal(
+ conn_rec* connection) {
+ ConnectionContext* context = GetConnContextInternal(connection);
+ return (context != NULL) ? context->master_context.get() : NULL;
+}
+
+SlaveConnectionContext* GetSlaveConnectionContextInternal(
+ conn_rec* connection) {
+ ConnectionContext* context = GetConnContextInternal(connection);
+ return (context != NULL) ? context->slave_context.get() : NULL;
+}
+
+} // namespace
+
+const SpdyServerConfig* GetServerConfig(server_rec* server) {
+ return GetServerConfigInternal(server);
+}
+
+const SpdyServerConfig* GetServerConfig(conn_rec* connection) {
+ return GetServerConfigInternal(connection->base_server);
+}
+
+const SpdyServerConfig* GetServerConfig(request_rec* request) {
+ return GetServerConfigInternal(request->server);
+}
+
+SpdyServerConfig* GetServerConfig(cmd_parms* command) {
+ return GetServerConfigInternal(command->server);
+}
+
+MasterConnectionContext* CreateMasterConnectionContext(conn_rec* connection,
+ bool using_ssl) {
+ ConnectionContext* context = SetConnContextInternal(
+ connection, new MasterConnectionContext(using_ssl), NULL);
+ return context->master_context.get();
+}
+
+SlaveConnectionContext* CreateSlaveConnectionContext(conn_rec* connection) {
+ ConnectionContext* context = SetConnContextInternal(
+ connection, NULL, new SlaveConnectionContext());
+ return context->slave_context.get();
+}
+
+bool HasMasterConnectionContext(conn_rec* connection) {
+ return GetMasterConnectionContextInternal(connection) != NULL;
+}
+
+bool HasSlaveConnectionContext(conn_rec* connection) {
+ return GetSlaveConnectionContextInternal(connection) != NULL;
+}
+
+MasterConnectionContext* GetMasterConnectionContext(conn_rec* connection) {
+ MasterConnectionContext* context =
+ GetMasterConnectionContextInternal(connection);
+ DCHECK(context != NULL);
+ return context;
+}
+
+SlaveConnectionContext* GetSlaveConnectionContext(conn_rec* connection) {
+ SlaveConnectionContext* context =
+ GetSlaveConnectionContextInternal(connection);
+ DCHECK(context != NULL);
+ return context;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_APACHE_CONFIG_UTIL_H_
+#define MOD_SPDY_APACHE_CONFIG_UTIL_H_
+
+#include "httpd.h"
+#include "http_config.h"
+
+namespace mod_spdy {
+
+class MasterConnectionContext;
+class SlaveConnectionContext;
+class SpdyServerConfig;
+class SpdyStream;
+
+// Get the server configuration associated with the given object. The
+// configuration object is returned const, since by the time these functions
+// are being used, the configuration should be treated as read-only.
+const SpdyServerConfig* GetServerConfig(server_rec* server);
+const SpdyServerConfig* GetServerConfig(conn_rec* connection);
+const SpdyServerConfig* GetServerConfig(request_rec* request);
+
+// Get the server configuration associated with the given configuration command
+// parameters. Since this is for setting the configuration (rather than just
+// reading it), the configuration object is returned non-const.
+SpdyServerConfig* GetServerConfig(cmd_parms* command);
+
+// Allocate a new MasterConnectionContext object for a master connection in the given
+// connection's pool, attach it to the connection's config vector, and return
+// it. Cannot be called on connection which previously was passed to
+// Create[Master|Slave]ConnectionContext.
+MasterConnectionContext* CreateMasterConnectionContext(
+ conn_rec* connection, bool using_ssl);
+
+// Allocate a new ConnectionContext object for a slave connection in the given
+// connection's pool, attach it to the connection's config vector, and return
+// it. Cannot be called on connection which previously was passed to
+// Create[Master|Slave]ConnectionContext.
+SlaveConnectionContext* CreateSlaveConnectionContext(conn_rec* connection);
+
+// Returns true if the connection has had a master connection context set.
+// We expect the result to be true for outgoing connections for which
+// mod_spdy is enabled on the server and which are using SSL, and on which
+// the pre-connection hook has fired.
+bool HasMasterConnectionContext(conn_rec* connection);
+
+// Returns true if the connection has had a slave connection context set.
+bool HasSlaveConnectionContext(conn_rec* connection);
+
+// Get the master connection context that was set on this connection
+// by a call to CreateMasterConnectionContext. Precondition:
+// HasMasterConnectionContext has been called, and returned true.
+MasterConnectionContext* GetMasterConnectionContext(conn_rec* connection);
+
+// Get the slave connection context that was set on this connection
+// by a call to CreateSlaveConnectionContext.
+// Precondition: HasSlaveConnectionContext has been called, and returned true.
+SlaveConnectionContext* GetSlaveConnectionContext(conn_rec* connection);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_CONFIG_UTIL_H_
--- /dev/null
+// Copyright 2010 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.
+
+// There are a number of things that every output filter should do, according
+// to <http://httpd.apache.org/docs/2.3/developer/output-filters.html>. In
+// short, these things are:
+//
+// - Respect FLUSH and EOS metadata buckets, and pass other metadata buckets
+// down the chain. Ignore all buckets after an EOS.
+//
+// - Don't allocate long-lived memory on every invocation. In particular, if
+// you need a temp brigade, allocate it once and then reuse it each time.
+//
+// - Never pass an empty brigade down the chain, but be ready to accept one
+// and do nothing.
+//
+// - Calling apr_brigade_destroy can be dangerous; prefer using
+// apr_brigade_cleanup instead.
+//
+// - Don't read the entire brigade into memory at once; the brigade may, for
+// example, contain a FILE bucket representing a 42 GB file. Instead, use
+// apr_bucket_read to read a reasonable portion of the bucket, put the
+// resulting (small) bucket into a temp brigade, pass it down the chain,
+// and then clean up the temp brigade before continuing.
+//
+// - If a bucket is to be saved beyond the scope of the filter invocation
+// that first received it, it must be "set aside" using the
+// apr_bucket_setaside macro.
+//
+// - When reading a bucket, first use a non-blocking read; if it fails with
+// APR_EAGAIN, send a FLUSH bucket down the chain, and then read the bucket
+// with a blocking read.
+//
+// This code attempts to follow these rules.
+
+#include "mod_spdy/apache/filters/http_to_spdy_filter.h"
+
+#include "apr_strings.h"
+
+#include "base/logging.h"
+#include "mod_spdy/apache/pool_util.h" // for AprStatusString
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/version.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+const char* kModSpdyVersion = MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+
+} // namespace
+
+namespace mod_spdy {
+
+HttpToSpdyFilter::HttpToSpdyFilter(const SpdyServerConfig* config,
+ SpdyStream* stream)
+ : receiver_(config, stream),
+ converter_(stream->spdy_version(), &receiver_),
+ eos_bucket_received_(false) {}
+
+HttpToSpdyFilter::~HttpToSpdyFilter() {}
+
+// Check if the SPDY stream has been aborted; if so, mark the connection object
+// as having been aborted and return APR_ECONNABORTED. Hopefully, this will
+// convince Apache to shut down processing for this (slave) connection, thus
+// allowing this stream's thread to complete and exit.
+#define RETURN_IF_STREAM_ABORT(filter) \
+ do { \
+ if ((filter)->c->aborted || receiver_.stream_->is_aborted()) { \
+ (filter)->c->aborted = true; \
+ return APR_ECONNABORTED; \
+ } \
+ } while (false)
+
+apr_status_t HttpToSpdyFilter::Write(ap_filter_t* filter,
+ apr_bucket_brigade* input_brigade) {
+ // This is a NETWORK-level filter, so there shouldn't be any filter after us.
+ if (filter->next != NULL) {
+ LOG(WARNING) << "HttpToSpdyFilter is not the last filter in the chain "
+ << "(it is followed by " << filter->next->frec->name << ")";
+ }
+
+ // According to the page at
+ // http://httpd.apache.org/docs/2.3/developer/output-filters.html
+ // we should never pass an empty brigade down the chain, but to be safe, we
+ // should be prepared to accept one and do nothing.
+ if (APR_BRIGADE_EMPTY(input_brigade)) {
+ LOG(INFO) << "HttpToSpdyFilter received an empty brigade.";
+ return APR_SUCCESS;
+ }
+
+ // Loop through the brigade, reading and sending data. We delete each bucket
+ // once we have successfully consumed it, before moving on to the next
+ // bucket. There are two reasons to delete buckets as we go:
+ //
+ // 1) Some output filters (such as mod_deflate) that come before us will
+ // expect us to empty out the brigade that they give us before we
+ // return. If we don't do so, the second time they call us we'll see
+ // all those same buckets again (along with the new buckets).
+ //
+ // 2) Some bucket types such as FILE don't store their data in memory, and
+ // when read, split into two buckets: one containing some data, and the
+ // other representing the rest of the file. If we read in all buckets
+ // in the brigade without deleting ones we're done with, we will
+ // eventually read the whole file into memory; by deleting buckets as we
+ // go, only a portion of the file is in memory at a time.
+ while (!APR_BRIGADE_EMPTY(input_brigade)) {
+ apr_bucket* bucket = APR_BRIGADE_FIRST(input_brigade);
+
+ if (APR_BUCKET_IS_METADATA(bucket)) {
+ if (APR_BUCKET_IS_EOS(bucket)) {
+ // EOS bucket -- there should be no more data buckets in this stream.
+ eos_bucket_received_ = true;
+ RETURN_IF_STREAM_ABORT(filter);
+ converter_.Flush();
+ } else if (APR_BUCKET_IS_FLUSH(bucket)) {
+ // FLUSH bucket -- call Send() immediately and flush the data buffer.
+ RETURN_IF_STREAM_ABORT(filter);
+ converter_.Flush();
+ } else {
+ // Unknown metadata bucket. This bucket has no meaning to us, and
+ // there's no further filter to pass it to, so we just ignore it.
+ }
+ } else if (eos_bucket_received_) {
+ // We shouldn't be getting any data buckets after an EOS (since this is a
+ // connection-level filter, we do sometimes see other metadata buckets
+ // after the EOS). If we do get them, ignore them.
+ LOG(INFO) << "HttpToSpdyFilter received " << bucket->type->name
+ << " bucket after an EOS (and ignored it).";
+ } else {
+ // Data bucket -- get ready to read.
+ const char* data = NULL;
+ apr_size_t data_length = 0;
+
+ // First, try a non-blocking read.
+ apr_status_t status = apr_bucket_read(bucket, &data, &data_length,
+ APR_NONBLOCK_READ);
+ if (status == APR_SUCCESS) {
+ RETURN_IF_STREAM_ABORT(filter);
+ if (!converter_.ProcessInput(data, static_cast<size_t>(data_length))) {
+ // Parse failure. The parser will have already logged an error.
+ return APR_EGENERAL;
+ }
+ } else if (APR_STATUS_IS_EAGAIN(status)) {
+ // Non-blocking read failed with EAGAIN, so try again with a blocking
+ // read (but flush first, in case we block for a long time).
+ RETURN_IF_STREAM_ABORT(filter);
+ converter_.Flush();
+ status = apr_bucket_read(bucket, &data, &data_length, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ LOG(ERROR) << "Blocking read failed with status " << status << ": "
+ << AprStatusString(status);
+ // Since we didn't successfully consume this bucket, don't delete it;
+ // rather, leave it (and any remaining buckets) in the brigade.
+ return status; // failure
+ }
+ RETURN_IF_STREAM_ABORT(filter);
+ if (!converter_.ProcessInput(data, static_cast<size_t>(data_length))) {
+ // Parse failure. The parser will have already logged an error.
+ return APR_EGENERAL;
+ }
+ } else {
+ // Since we didn't successfully consume this bucket, don't delete it;
+ // rather, leave it (and any remaining buckets) in the brigade.
+ return status; // failure
+ }
+ }
+
+ // We consumed this bucket successfully, so delete it and move on to the
+ // next.
+ apr_bucket_delete(bucket);
+ }
+
+ // We went through the whole brigade successfully, so it must be empty when
+ // we return (see http://code.google.com/p/mod-spdy/issues/detail?id=17).
+ DCHECK(APR_BRIGADE_EMPTY(input_brigade));
+ return APR_SUCCESS;
+}
+
+HttpToSpdyFilter::ReceiverImpl::ReceiverImpl(const SpdyServerConfig* config,
+ SpdyStream* stream)
+ : config_(config), stream_(stream) {
+ DCHECK(config_);
+ DCHECK(stream_);
+}
+
+HttpToSpdyFilter::ReceiverImpl::~ReceiverImpl() {}
+
+void HttpToSpdyFilter::ReceiverImpl::ReceiveSynReply(
+ net::SpdyHeaderBlock* headers, bool flag_fin) {
+ DCHECK(headers);
+ if (config_->send_version_header()) {
+ (*headers)[http::kXModSpdy] = kModSpdyVersion;
+ }
+ // For client-requested streams, we should send a SYN_REPLY. For
+ // server-pushed streams, the SpdySession has already sent an initial
+ // SYN_STREAM with FLAG_UNIDIRECTIONAL and minimal server push headers, so we
+ // now follow up with a HEADERS frame with the response headers.
+ if (stream_->is_server_push()) {
+ stream_->SendOutputHeaders(*headers, flag_fin);
+ } else {
+ stream_->SendOutputSynReply(*headers, flag_fin);
+ }
+}
+
+void HttpToSpdyFilter::ReceiverImpl::ReceiveData(
+ base::StringPiece data, bool flag_fin) {
+ stream_->SendOutputDataFrame(data, flag_fin);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_APACHE_FILTERS_HTTP_TO_SPDY_FILTER_H_
+#define MOD_SPDY_APACHE_FILTERS_HTTP_TO_SPDY_FILTER_H_
+
+#include <string>
+
+#include "apr_buckets.h"
+#include "util_filter.h"
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+namespace mod_spdy {
+
+class SpdyServerConfig;
+class SpdyStream;
+
+// An Apache filter for converting HTTP data into SPDY frames and sending them
+// to the output queue of a SpdyStream object. This is intended to be the
+// outermost filter in the output chain of one of our slave connections,
+// essentially taking the place of the network socket.
+//
+// In a previous implementation of this filter, we made this a TRANSCODE-level
+// filter rather than a NETWORK-level filter; this had the advantage that we
+// could pull HTTP header data directly from the Apache request object, rather
+// than having to parse the headers. However, it had the disadvantage of being
+// fragile -- for example, we had an additional output filter whose sole job
+// was to deceive Apache into not chunking the response body, and several
+// different hooks to try to make sure our output filters stayed in place even
+// in the face of Apache's weird error-handling paths. Also, using a
+// NETWORK-level filter decreases the likelihood that we'll break other modules
+// that try to use connection-level filters.
+class HttpToSpdyFilter {
+ public:
+ HttpToSpdyFilter(const SpdyServerConfig* config, SpdyStream* stream);
+ ~HttpToSpdyFilter();
+
+ // Read data from the given brigade and write the result through the given
+ // filter. This method is responsible for driving the HTTP to SPDY conversion
+ // process.
+ apr_status_t Write(ap_filter_t* filter, apr_bucket_brigade* input_brigade);
+
+ private:
+ class ReceiverImpl : public HttpToSpdyConverter::SpdyReceiver {
+ public:
+ ReceiverImpl(const SpdyServerConfig* config, SpdyStream* stream);
+ virtual ~ReceiverImpl();
+ virtual void ReceiveSynReply(net::SpdyHeaderBlock* headers, bool flag_fin);
+ virtual void ReceiveData(base::StringPiece data, bool flag_fin);
+
+ private:
+ friend class HttpToSpdyFilter;
+ const SpdyServerConfig* config_;
+ SpdyStream* const stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReceiverImpl);
+ };
+
+ ReceiverImpl receiver_;
+ HttpToSpdyConverter converter_;
+ bool eos_bucket_received_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpToSpdyFilter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_FILTERS_HTTP_TO_SPDY_FILTER_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/filters/http_to_spdy_filter.h"
+
+#include <string>
+
+#include "httpd.h"
+#include "apr_buckets.h"
+#include "apr_tables.h"
+#include "util_filter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "mod_spdy/common/version.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using mod_spdy::testing::IsDataFrame;
+using mod_spdy::testing::IsHeaders;
+using mod_spdy::testing::IsSynReply;
+using testing::Pointee;
+
+namespace {
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+ MOCK_METHOD4(StartServerPush,
+ mod_spdy::SpdyServerPushInterface::PushStatus(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers));
+};
+
+class HttpToSpdyFilterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ HttpToSpdyFilterTest()
+ : spdy_version_(GetParam()),
+ shared_window_(net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize),
+ connection_(static_cast<conn_rec*>(
+ apr_pcalloc(local_.pool(), sizeof(conn_rec)))),
+ ap_filter_(static_cast<ap_filter_t*>(
+ apr_pcalloc(local_.pool(), sizeof(ap_filter_t)))),
+ bucket_alloc_(apr_bucket_alloc_create(local_.pool())),
+ brigade_(apr_brigade_create(local_.pool(), bucket_alloc_)) {
+ // Set up our Apache data structures. To keep things simple, we set only
+ // the bare minimum of necessary fields, and rely on apr_pcalloc to zero
+ // all others.
+ connection_->pool = local_.pool();
+ ap_filter_->c = connection_;
+ }
+
+ protected:
+ void AddHeapBucket(base::StringPiece str) {
+ APR_BRIGADE_INSERT_TAIL(brigade_, apr_bucket_heap_create(
+ str.data(), str.size(), NULL, bucket_alloc_));
+ }
+
+ void AddImmortalBucket(base::StringPiece str) {
+ APR_BRIGADE_INSERT_TAIL(brigade_, apr_bucket_immortal_create(
+ str.data(), str.size(), bucket_alloc_));
+ }
+
+ void AddFlushBucket() {
+ APR_BRIGADE_INSERT_TAIL(brigade_, apr_bucket_flush_create(bucket_alloc_));
+ }
+
+ void AddEosBucket() {
+ APR_BRIGADE_INSERT_TAIL(brigade_, apr_bucket_eos_create(bucket_alloc_));
+ }
+
+ apr_status_t WriteBrigade(mod_spdy::HttpToSpdyFilter* filter) {
+ return filter->Write(ap_filter_, brigade_);
+ }
+
+ void ExpectSynReply(net::SpdyStreamId stream_id,
+ const net::SpdyHeaderBlock& headers,
+ bool flag_fin) {
+ net::SpdyFrameIR* raw_frame = NULL;
+ ASSERT_TRUE(output_queue_.Pop(&raw_frame));
+ ASSERT_TRUE(raw_frame != NULL);
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsSynReply(stream_id, flag_fin, headers));
+ }
+
+ void ExpectHeaders(net::SpdyStreamId stream_id,
+ const net::SpdyHeaderBlock& headers,
+ bool flag_fin) {
+ net::SpdyFrameIR* raw_frame = NULL;
+ ASSERT_TRUE(output_queue_.Pop(&raw_frame));
+ ASSERT_TRUE(raw_frame != NULL);
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsHeaders(stream_id, flag_fin, headers));
+ }
+
+ void ExpectDataFrame(net::SpdyStreamId stream_id, base::StringPiece data,
+ bool flag_fin) {
+ net::SpdyFrameIR* raw_frame = NULL;
+ ASSERT_TRUE(output_queue_.Pop(&raw_frame));
+ ASSERT_TRUE(raw_frame != NULL);
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsDataFrame(stream_id, flag_fin, data));
+ }
+
+ void ExpectOutputQueueEmpty() {
+ net::SpdyFrameIR* frame;
+ EXPECT_FALSE(output_queue_.Pop(&frame));
+ }
+
+ const char* status_header_name() const {
+ return (spdy_version_ < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Status : mod_spdy::spdy::kSpdy3Status);
+ }
+
+ const char* version_header_name() const {
+ return (spdy_version_ < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Version : mod_spdy::spdy::kSpdy3Version);
+ }
+
+ const mod_spdy::spdy::SpdyVersion spdy_version_;
+ mod_spdy::SpdyFramePriorityQueue output_queue_;
+ mod_spdy::SharedFlowControlWindow shared_window_;
+ MockSpdyServerPushInterface pusher_;
+ mod_spdy::LocalPool local_;
+ conn_rec* const connection_;
+ ap_filter_t* const ap_filter_;
+ apr_bucket_alloc_t* const bucket_alloc_;
+ apr_bucket_brigade* const brigade_;
+};
+
+TEST_P(HttpToSpdyFilterTest, ResponseWithContentLength) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send part of the header data into the filter:
+ AddImmortalBucket("HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get nothing out yet:
+ ExpectOutputQueueEmpty();
+
+ // Send the rest of the header data into the filter:
+ AddImmortalBucket("Content-Length: 12000\r\n"
+ "Content-Type: text/html\r\n"
+ "Host: www.example.com\r\n"
+ "\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get a single SYN_REPLY frame out with all the headers:
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers[mod_spdy::http::kContentLength] = "12000";
+ expected_headers[mod_spdy::http::kContentType] = "text/html";
+ expected_headers[mod_spdy::http::kHost] = "www.example.com";
+ expected_headers[status_header_name()] = "200";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ expected_headers[mod_spdy::http::kXModSpdy] =
+ MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+ ExpectSynReply(stream_id, expected_headers, false);
+ ExpectOutputQueueEmpty();
+
+ // Now send in some body data, with a FLUSH bucket:
+ AddHeapBucket(std::string(1000, 'a'));
+ AddFlushBucket();
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get a single data frame out, containing the data we just sent:
+ ExpectDataFrame(stream_id, std::string(1000, 'a'), false);
+ ExpectOutputQueueEmpty();
+
+ // Send in some more body data, this time with no FLUSH bucket:
+ AddHeapBucket(std::string(2000, 'b'));
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get nothing more out yet (because there's too little data to be
+ // worth sending a frame):
+ ExpectOutputQueueEmpty();
+
+ // Send lots more body data, again with a FLUSH bucket:
+ AddHeapBucket(std::string(3000, 'c'));
+ AddFlushBucket();
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // This time, we should get two data frames out.
+ ExpectDataFrame(stream_id, std::string(2000, 'b') + std::string(2096, 'c'),
+ false);
+ ExpectDataFrame(stream_id, std::string(904, 'c'), false);
+ ExpectOutputQueueEmpty();
+
+ // Finally, send a bunch more data, followed by an EOS bucket:
+ AddHeapBucket(std::string(6000, 'd'));
+ AddEosBucket();
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // We should get two last data frames, the latter having FLAG_FIN set.
+ ExpectDataFrame(stream_id, std::string(4096, 'd'), false);
+ ExpectDataFrame(stream_id, std::string(1904, 'd'), true);
+ ExpectOutputQueueEmpty();
+}
+
+TEST_P(HttpToSpdyFilterTest, ChunkedResponse) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send part of the header data into the filter:
+ AddImmortalBucket("HTTP/1.1 200 OK\r\n"
+ "Keep-Alive: timeout=120\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get nothing out yet:
+ ExpectOutputQueueEmpty();
+
+ // Send the rest of the header data into the filter:
+ AddImmortalBucket("Content-Type: text/html\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Host: www.example.com\r\n"
+ "\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get a single SYN_REPLY frame out with all the headers:
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers[mod_spdy::http::kContentType] = "text/html";
+ expected_headers[mod_spdy::http::kHost] = "www.example.com";
+ expected_headers[status_header_name()] = "200";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ expected_headers[mod_spdy::http::kXModSpdy] =
+ MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+ ExpectSynReply(stream_id, expected_headers, false);
+ ExpectOutputQueueEmpty();
+
+ // Now send in some body data, with a FLUSH bucket:
+ AddImmortalBucket("1B\r\n");
+ AddImmortalBucket("abcdefghijklmnopqrstuvwxyz\n\r\n");
+ AddImmortalBucket("17\r\n");
+ AddImmortalBucket("That was ");
+ AddFlushBucket();
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get a single data frame out, containing the data we just sent:
+ ExpectDataFrame(stream_id, "abcdefghijklmnopqrstuvwxyz\nThat was ", false);
+ ExpectOutputQueueEmpty();
+
+ // Send in some more body data, this time with no FLUSH bucket:
+ AddImmortalBucket("the alphabet.\n\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get nothing more out yet (because there's too little data to be
+ // worth sending a frame):
+ ExpectOutputQueueEmpty();
+
+ // Finally, terminate the response:
+ AddImmortalBucket("0\r\n\r\n");
+ AddEosBucket();
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // We should get the last data frame, with FLAG_FIN set.
+ ExpectDataFrame(stream_id, "the alphabet.\n", true);
+ ExpectOutputQueueEmpty();
+}
+
+TEST_P(HttpToSpdyFilterTest, RedirectResponse) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 5;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send part of the header data into the filter:
+ AddImmortalBucket("HTTP/1.1 301 Moved Permanently\r\n"
+ "Location: http://www.example.net/\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get nothing out yet:
+ ExpectOutputQueueEmpty();
+
+ // Signal the end of the leading headers:
+ AddImmortalBucket("\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get a single SYN_REPLY frame out. This response has no body, so
+ // FLAG_FIN should be set.
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers["location"] = "http://www.example.net/";
+ expected_headers[status_header_name()] = "301";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ expected_headers[mod_spdy::http::kXModSpdy] =
+ MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+ ExpectSynReply(stream_id, expected_headers, true);
+ ExpectOutputQueueEmpty();
+}
+
+// Test that the filter accepts empty brigades.
+TEST_P(HttpToSpdyFilterTest, AcceptEmptyBrigade) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 5;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send the header data into the filter:
+ AddImmortalBucket("HTTP/1.1 200 OK\r\n"
+ "Content-Length: 6\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers[mod_spdy::http::kContentLength] = "6";
+ expected_headers[mod_spdy::http::kContentType] = "text/plain";
+ expected_headers[status_header_name()] = "200";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ expected_headers[mod_spdy::http::kXModSpdy] =
+ MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+ ExpectSynReply(stream_id, expected_headers, false);
+ ExpectOutputQueueEmpty();
+
+ // Send in some body data:
+ AddImmortalBucket("foo");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+ ExpectOutputQueueEmpty();
+
+ // Run the filter again, with an empty brigade. It should accept it and do
+ // nothing.
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+ ExpectOutputQueueEmpty();
+
+ // Send in the rest of the body data.
+ AddImmortalBucket("bar");
+ AddEosBucket();
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+ ExpectDataFrame(stream_id, "foobar", true);
+ ExpectOutputQueueEmpty();
+}
+
+// Test that the filter behaves correctly when a stream is aborted halfway
+// through producing output.
+TEST_P(HttpToSpdyFilterTest, StreamAbort) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 7;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority =
+ mod_spdy::LowestSpdyPriorityForVersion(spdy_version_);
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send the header data into the filter:
+ AddImmortalBucket("HTTP/1.1 200 OK\r\n"
+ "Content-Length: 6\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers[mod_spdy::http::kContentLength] = "6";
+ expected_headers[mod_spdy::http::kContentType] = "text/plain";
+ expected_headers[status_header_name()] = "200";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ expected_headers[mod_spdy::http::kXModSpdy] =
+ MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+ ExpectSynReply(stream_id, expected_headers, false);
+ ExpectOutputQueueEmpty();
+
+ // Send in some body data:
+ AddImmortalBucket("foo");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+ ExpectOutputQueueEmpty();
+
+ // Abort the stream, and then try to send in more data. We should get back
+ // ECONNABORTED, the brigade should remain unconsumed, and the connection
+ // should be marked as aborted.
+ stream.AbortSilently();
+ AddImmortalBucket("bar");
+ AddEosBucket();
+ ASSERT_FALSE(connection_->aborted);
+ ASSERT_TRUE(APR_STATUS_IS_ECONNABORTED(WriteBrigade(&http_to_spdy_filter)));
+ EXPECT_FALSE(APR_BRIGADE_EMPTY(brigade_));
+ ExpectOutputQueueEmpty();
+ ASSERT_TRUE(connection_->aborted);
+}
+
+TEST_P(HttpToSpdyFilterTest, ServerPushedStream) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 4;
+ const net::SpdyStreamId associated_stream_id = 3;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send the response data into the filter:
+ AddImmortalBucket("HTTP/1.1 200 OK\r\n"
+ "Content-Length: 20\r\n"
+ "Content-Type: text/css\r\n"
+ "\r\n"
+ "BODY { color: red; }");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Since this is a server push stream, the SpdySession should have earlier
+ // sent a SYN_STREAM with FLAG_UNIDIRECTIONAL, and we now expect to see a
+ // HEADERS frame from the filter (rather than a SYN_REPLY):
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers[mod_spdy::http::kContentLength] = "20";
+ expected_headers[mod_spdy::http::kContentType] = "text/css";
+ expected_headers[status_header_name()] = "200";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ expected_headers[mod_spdy::http::kXModSpdy] =
+ MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+ ExpectHeaders(stream_id, expected_headers, false);
+ // And also the pushed data:
+ ExpectDataFrame(stream_id, "BODY { color: red; }", true);
+ ExpectOutputQueueEmpty();
+}
+
+TEST_P(HttpToSpdyFilterTest, DoNotSendVersionHeaderWhenAskedNotTo) {
+ // Set up our data structures that we're testing:
+ const net::SpdyStreamId stream_id = 5;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::SpdyServerConfig config;
+ config.set_send_version_header(false);
+ mod_spdy::HttpToSpdyFilter http_to_spdy_filter(&config, &stream);
+
+ // Send the response into the filter:
+ AddImmortalBucket("HTTP/1.1 301 Moved Permanently\r\n"
+ "Location: http://www.example.net/\r\n"
+ "\r\n");
+ ASSERT_EQ(APR_SUCCESS, WriteBrigade(&http_to_spdy_filter));
+ EXPECT_TRUE(APR_BRIGADE_EMPTY(brigade_));
+
+ // Expect to get a single SYN_REPLY frame out. This response has no body, so
+ // FLAG_FIN should be set. There should be no version header.
+ net::SpdyHeaderBlock expected_headers;
+ expected_headers["location"] = "http://www.example.net/";
+ expected_headers[status_header_name()] = "301";
+ expected_headers[version_header_name()] = "HTTP/1.1";
+ ExpectSynReply(stream_id, expected_headers, true);
+ ExpectOutputQueueEmpty();
+}
+
+// Run each test over SPDY/2, SPDY/3, and SPDY/3.1.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, HttpToSpdyFilterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/filters/server_push_filter.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h" // for StringToUint
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_stream.h"
+
+namespace mod_spdy {
+
+namespace {
+
+// Utility function passed to apr_table_do:
+int AddOneHeader(void* headers, const char* key, const char* value) {
+ mod_spdy::MergeInHeader(
+ key, value, static_cast<net::SpdyHeaderBlock*>(headers));
+ return 1; // return zero to stop, or non-zero to continue iterating
+}
+
+// Modify *source to remove whitespace characters from the front.
+void AbsorbWhiteSpace(base::StringPiece* source) {
+ *source = source->substr(source->find_first_not_of(" \n\r\t"));
+}
+
+// If the first thing in *source is '"foobar"', set out to 'foobar', modify
+// *source to skip past both quotes and any whitespace thereafter, and return
+// true. Otherwise return false.
+bool ParseQuotedString(base::StringPiece* source, std::string* out) {
+ if (source->empty() || (*source)[0] != '"') {
+ return false; // failure: no open quote
+ }
+ const size_t close = source->find('"', 1);
+ if (close == base::StringPiece::npos) {
+ return false; // failure: no close quote
+ }
+ source->substr(1, close - 1).CopyToString(out);
+ *source = source->substr(close + 1);
+ AbsorbWhiteSpace(source);
+ return true;
+}
+
+// If the next character in *source is c, modify *source to skip past the
+// character and any whitespace thereafter, and return true. Otherwise return
+// false.
+bool ParseSeparator(char c, base::StringPiece* source) {
+ if (source->empty() || (*source)[0] != c) {
+ return false;
+ }
+ *source = source->substr(1);
+ AbsorbWhiteSpace(source);
+ return true;
+}
+
+// If the next part of *source looks like ':2' (for some value of 2), parse the
+// number, store it in *out, and modify *source to skip past it. Otherwise,
+// just leave *source unchanged. See ParseAssociatedContent for the full
+// expected format of *source.
+net::SpdyPriority ParseOptionalPriority(SpdyStream* spdy_stream,
+ base::StringPiece* source) {
+ const net::SpdyPriority lowest_priority =
+ LowestSpdyPriorityForVersion(spdy_stream->spdy_version());
+ if (!ParseSeparator(':', source)) {
+ // It's okay for the ":priority" to not be there. In that case, we default
+ // to minimal priority.
+ return lowest_priority;
+ }
+ const size_t end = source->find_first_not_of("0123456789");
+ const base::StringPiece number = source->substr(0, end);
+ unsigned priority;
+ if (!StringToUint(number, &priority)) {
+ LOG(INFO) << "Invalid priority value in X-Associated-Content: '"
+ << number << "'";
+ return lowest_priority;
+ }
+ *source = source->substr(end);
+ AbsorbWhiteSpace(source);
+ // Clamp the priority to a legal value (larger numbers represent lower
+ // priorities, so we must not return a number greater than lowest_priority).
+ return (priority > lowest_priority ? lowest_priority : priority);
+}
+
+} // namespace
+
+ServerPushFilter::ServerPushFilter(SpdyStream* stream, request_rec* request,
+ const SpdyServerConfig* server_cfg)
+ : stream_(stream), request_(request), server_cfg_(server_cfg) {
+ DCHECK(stream_);
+ DCHECK(request_);
+}
+
+ServerPushFilter::~ServerPushFilter() {}
+
+apr_status_t ServerPushFilter::Write(ap_filter_t* filter,
+ apr_bucket_brigade* input_brigade) {
+ DCHECK_EQ(request_, filter->r);
+ // We only do server pushes for SPDY v3 and later. Also, to avoid infinite
+ // push loops, we don't allow push streams to invoke further push streams
+ // beyond a specified depth.
+ if (stream_->spdy_version() >= spdy::SPDY_VERSION_3 &&
+ stream_->server_push_depth() < server_cfg_->max_server_push_depth()) {
+ // Parse and start pushes for each X-Associated-Content header, if any.
+ // (Note that APR tables allow multiple entries with the same key, just
+ // like HTTP headers.)
+ apr_table_do(
+ OnXAssociatedContent, // function to call on each key/value pair
+ this, // void* to be passed as first arg to function
+ request_->headers_out, // the apr_table_t to iterate over
+ // Varargs: zero or more char* keys to iterate over, followed by NULL
+ http::kXAssociatedContent, NULL);
+ // We need to give the same treatment to err_headers_out as we just gave to
+ // headers_out. Depending on how the X-Associated-Content header was set,
+ // it might end up in either one. For example, using a mod_headers Header
+ // directive will put the header in headers_out, but using a PHP header()
+ // function call will put the header in err_headers_out.
+ apr_table_do(OnXAssociatedContent, this, request_->err_headers_out,
+ http::kXAssociatedContent, NULL);
+ }
+ // Even in cases where we forbid pushes from this stream, we still purge the
+ // X-Associated-Content header (from both headers_out and err_headers_out).
+ apr_table_unset(request_->headers_out, http::kXAssociatedContent);
+ apr_table_unset(request_->err_headers_out, http::kXAssociatedContent);
+
+ // Remove ourselves from the filter chain.
+ ap_remove_output_filter(filter);
+ // Pass the data through unchanged.
+ return ap_pass_brigade(filter->next, input_brigade);
+}
+
+void ServerPushFilter::ParseXAssociatedContentHeader(base::StringPiece value) {
+ AbsorbWhiteSpace(&value);
+ bool first_url = true;
+
+ while (!value.empty()) {
+ // The URLs should be separated by commas, so a comma should proceed each
+ // URL except the first.
+ if (first_url) {
+ first_url = false;
+ } else if (!ParseSeparator(',', &value)) {
+ LOG(INFO) << "Parse error in X-Associated-Content: missing comma";
+ return;
+ }
+
+ // Get a quoted URL string.
+ std::string url;
+ if (!ParseQuotedString(&value, &url)) {
+ LOG(INFO) << "Parse error in X-Associated-Content: expected quoted URL";
+ return;
+ }
+
+ // The URL may optionally be followed by a priority. If the priority is
+ // not there, use the lowest-importance priority by default.
+ net::SpdyPriority priority = ParseOptionalPriority(stream_, &value);
+
+ // Try to parse the URL string. If it does not form a valid URL, log an
+ // error and skip past this entry.
+ apr_uri_t parsed_url;
+ {
+ const apr_status_t status =
+ apr_uri_parse(request_->pool, url.c_str(), &parsed_url);
+ if (status != APR_SUCCESS) {
+ LOG(ERROR) << "Invalid URL in X-Associated-Content: '" << url << "'";
+ continue;
+ }
+ }
+
+ // Populate the fake request headers for the pushed stream.
+ net::SpdyHeaderBlock request_headers;
+ // Start off by pulling in certain headers from the associated stream's
+ // request headers.
+ apr_table_do(
+ AddOneHeader, // function to call on each key/value pair
+ &request_headers, // void* to be passed as first arg to function
+ request_->headers_in, // the apr_table_t to iterate over
+ // Varargs: zero or more char* keys to iterate over, followed by NULL
+ "accept", "accept-charset", "accept-datetime",
+ mod_spdy::http::kAcceptEncoding, "accept-language", "authorization",
+ "user-agent", NULL);
+ // Next, we populate special SPDY headers, using a combination of pushed
+ // URL and details from the associated request.
+ if (parsed_url.hostinfo != NULL) {
+ request_headers[spdy::kSpdy3Host] = parsed_url.hostinfo;
+ } else {
+ const char* host_header =
+ apr_table_get(request_->headers_in, http::kHost);
+ request_headers[spdy::kSpdy3Host] =
+ (host_header != NULL ? host_header :
+ request_->hostname != NULL ? request_->hostname : "");
+ }
+ request_headers[spdy::kSpdy3Method] = "GET";
+ request_headers[spdy::kSpdy3Scheme] =
+ (parsed_url.scheme != NULL ? parsed_url.scheme : "https");
+ request_headers[spdy::kSpdy3Version] = request_->protocol;
+ // Construct the path that we are pushing from the parsed URL.
+ // TODO(mdsteele): It'd be nice to support relative URLs.
+ {
+ std::string* path = &request_headers[spdy::kSpdy3Path];
+ path->assign(parsed_url.path == NULL ? "/" : parsed_url.path);
+ if (parsed_url.query != NULL) {
+ path->push_back('?');
+ path->append(parsed_url.query);
+ }
+ if (parsed_url.fragment != NULL) {
+ // It's a little weird to try to push a URL with a fragment in it, but
+ // if someone does so anyway, we may as well honor it.
+ path->push_back('#');
+ path->append(parsed_url.fragment);
+ }
+ }
+ // Finally, we set the HTTP referrer to be the associated stream's URL.
+ request_headers[http::kReferer] = request_->unparsed_uri;
+
+ // Try to perform the push. If it succeeds, we'll continue with parsing.
+ const SpdyServerPushInterface::PushStatus status =
+ stream_->StartServerPush(priority, request_headers);
+ switch (status) {
+ case SpdyServerPushInterface::PUSH_STARTED:
+ break; // success
+ case SpdyServerPushInterface::INVALID_REQUEST_HEADERS:
+ // This shouldn't happen unless there's a bug in the above code.
+ LOG(DFATAL) << "ParseAssociatedContent: invalid request headers";
+ return;
+ case SpdyServerPushInterface::ASSOCIATED_STREAM_INACTIVE:
+ case SpdyServerPushInterface::CANNOT_PUSH_EVER_AGAIN:
+ case SpdyServerPushInterface::TOO_MANY_CONCURRENT_PUSHES:
+ case SpdyServerPushInterface::PUSH_INTERNAL_ERROR:
+ // In any of these cases, any remaining pushes specified by the header
+ // are unlikely to succeed, so just stop parsing and quit.
+ LOG(INFO) << "Push failed while processing X-Associated-Content "
+ << "header (status=" << status << "). Skipping remainder.";
+ return;
+ default:
+ LOG(DFATAL) << "Invalid push status value: " << status;
+ return;
+ }
+ }
+}
+
+// static
+int ServerPushFilter::OnXAssociatedContent(
+ void* server_push_filter, const char* key, const char* value) {
+ static_cast<ServerPushFilter*>(server_push_filter)->
+ ParseXAssociatedContentHeader(value);
+ return 1; // return zero to stop, or non-zero to continue iterating
+}
+
+} // namespace mod_spdy
--- /dev/null
+// 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.
+
+#ifndef MOD_SPDY_APACHE_FILTERS_SERVER_PUSH_FILTER_H_
+#define MOD_SPDY_APACHE_FILTERS_SERVER_PUSH_FILTER_H_
+
+#include <string>
+
+#include "apr_buckets.h"
+#include "util_filter.h"
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/spdy_server_config.h"
+
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// An Apache filter for initiating SPDY server pushes based on the
+// X-Associated-Content response header, which may be set by e.g. mod_headers
+// or a CGI script.
+class ServerPushFilter {
+ public:
+ // Does not take ownership of either argument.
+ ServerPushFilter(SpdyStream* stream, request_rec* request,
+ const SpdyServerConfig* server_cfg);
+ ~ServerPushFilter();
+
+ // Read data from the given brigade and write the result through the given
+ // filter. This filter doesn't touch the data; it only looks at the request
+ // headers.
+ apr_status_t Write(ap_filter_t* filter, apr_bucket_brigade* input_brigade);
+
+ private:
+ // Parse the value of an X-Associated-Content header, and initiate any
+ // specified server pushes. The expected format of the header value is a
+ // comma-separated list of quoted URLs, each of which may optionally be
+ // followed by colon and a SPDY priority value. The URLs may be fully
+ // qualified URLs, or simply absolute paths (for the same scheme/host as the
+ // original request). If the optional priority is omitted for a URL, then it
+ // uses the lowest possible priority. Whitespace is permitted between
+ // tokens. For example:
+ //
+ // X-Associated-Content: "https://www.example.com/foo.css",
+ // "/bar/baz.js?x=y" : 1, "https://www.example.com/quux.css":3
+ void ParseXAssociatedContentHeader(base::StringPiece value);
+ // Utility function passed to apr_table_do. The first argument must point to
+ // a ServerPushFilter; this will call ParseXAssociatedContentHeader on it.
+ static int OnXAssociatedContent(void*, const char*, const char*);
+
+ SpdyStream* const stream_;
+ request_rec* const request_;
+ const SpdyServerConfig* server_cfg_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerPushFilter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_FILTERS_SERVER_PUSH_FILTER_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/filters/server_push_filter.h"
+
+#include <string>
+
+#include "httpd.h"
+#include "apr_buckets.h"
+#include "apr_tables.h"
+#include "util_filter.h"
+
+#include "base/strings/string_piece.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Contains;
+using testing::Eq;
+using testing::Pair;
+using testing::Return;
+
+namespace {
+
+const char* const kRefererUrl = "https://www.example.com/index.html";
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+ MOCK_METHOD4(StartServerPush,
+ mod_spdy::SpdyServerPushInterface::PushStatus(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers));
+};
+
+class ServerPushFilterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ ServerPushFilterTest()
+ : spdy_version_(GetParam()),
+ shared_window_(net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize),
+ connection_(static_cast<conn_rec*>(
+ apr_pcalloc(local_.pool(), sizeof(conn_rec)))),
+ request_(static_cast<request_rec*>(
+ apr_pcalloc(local_.pool(), sizeof(request_rec)))),
+ ap_filter_(static_cast<ap_filter_t*>(
+ apr_pcalloc(local_.pool(), sizeof(ap_filter_t)))),
+ bucket_alloc_(apr_bucket_alloc_create(local_.pool())),
+ brigade_(apr_brigade_create(local_.pool(), bucket_alloc_)) {
+ // Set up our Apache data structures. To keep things simple, we set only
+ // the bare minimum of necessary fields, and rely on apr_pcalloc to zero
+ // all others.
+ connection_->pool = local_.pool();
+ request_->pool = local_.pool();
+ request_->connection = connection_;
+ request_->headers_in = apr_table_make(local_.pool(), 5);
+ request_->headers_out = apr_table_make(local_.pool(), 5);
+ request_->err_headers_out = apr_table_make(local_.pool(), 5);
+ request_->protocol = const_cast<char*>("HTTP/1.1");
+ request_->unparsed_uri = const_cast<char*>(kRefererUrl);
+ ap_filter_->c = connection_;
+ ap_filter_->r = request_;
+ }
+
+ virtual void SetUp() {
+ ON_CALL(pusher_, StartServerPush(_, _, _, _)).WillByDefault(
+ Return(mod_spdy::SpdyServerPushInterface::PUSH_STARTED));
+ apr_table_setn(request_->headers_in, mod_spdy::http::kHost,
+ "www.example.com");
+ }
+
+ protected:
+ void WriteBrigade(mod_spdy::ServerPushFilter* filter) {
+ EXPECT_EQ(APR_SUCCESS, filter->Write(ap_filter_, brigade_));
+ }
+
+ const char* status_header_name() const {
+ return (spdy_version_ < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Status : mod_spdy::spdy::kSpdy3Status);
+ }
+
+ const char* version_header_name() const {
+ return (spdy_version_ < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Version : mod_spdy::spdy::kSpdy3Version);
+ }
+
+ const mod_spdy::spdy::SpdyVersion spdy_version_;
+ mod_spdy::SpdyFramePriorityQueue output_queue_;
+ mod_spdy::SharedFlowControlWindow shared_window_;
+ MockSpdyServerPushInterface pusher_;
+ mod_spdy::LocalPool local_;
+ conn_rec* const connection_;
+ request_rec* const request_;
+ ap_filter_t* const ap_filter_;
+ apr_bucket_alloc_t* const bucket_alloc_;
+ apr_bucket_brigade* const brigade_;
+};
+
+TEST_P(ServerPushFilterTest, SimpleXAssociatedContent) {
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ net::SpdyHeaderBlock headers1;
+ headers1[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ headers1[mod_spdy::spdy::kSpdy3Method] = "GET";
+ headers1[mod_spdy::spdy::kSpdy3Path] = "/foo/bar.css?q=12";
+ headers1[mod_spdy::spdy::kSpdy3Scheme] = "https";
+ headers1[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ headers1[mod_spdy::http::kReferer] = kRefererUrl;
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(2u), Eq(headers1)));
+
+ net::SpdyHeaderBlock headers2;
+ headers2[mod_spdy::spdy::kSpdy3Host] = "cdn.example.com:8080";
+ headers2[mod_spdy::spdy::kSpdy3Method] = "GET";
+ headers2[mod_spdy::spdy::kSpdy3Path] = "/images/foo.png";
+ headers2[mod_spdy::spdy::kSpdy3Scheme] = "https";
+ headers2[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ headers2[mod_spdy::http::kReferer] = kRefererUrl;
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(7u), Eq(headers2)));
+
+ net::SpdyHeaderBlock headers3;
+ headers3[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ headers3[mod_spdy::spdy::kSpdy3Method] = "GET";
+ headers3[mod_spdy::spdy::kSpdy3Path] = "/scripts/awesome.js";
+ headers3[mod_spdy::spdy::kSpdy3Scheme] = "https";
+ headers3[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ headers3[mod_spdy::http::kReferer] = kRefererUrl;
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(0u), Eq(headers3)));
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"https://www.example.com/foo/bar.css?q=12\":2,"
+ "\"https://cdn.example.com:8080/images/foo.png\","
+ "\"/scripts/awesome.js\":0");
+ WriteBrigade(&server_push_filter);
+ // The X-Associated-Content header should get removed.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+// Test that if there are multiple X-Associated-Content headers in the APR
+// table, we heed (and then remove) all of them.
+TEST_P(ServerPushFilterTest, MultipleXAssociatedContentHeaders) {
+ const net::SpdyStreamId stream_id = 13;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+ const net::SpdyPriority lowest =
+ mod_spdy::LowestSpdyPriorityForVersion(stream.spdy_version());
+
+ testing::Sequence s1, s2, s3, s4, s5;
+
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(2u),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x1.png")))).InSequence(s1);
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(0u),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x2.png")))).InSequence(s1);
+
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(lowest),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x3.png")))).InSequence(s2);
+
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(3u),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x4.png")))).InSequence(s3);
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(lowest),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x5.png")))).InSequence(s3);
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(1u),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x6.png")))).InSequence(s3);
+
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(2u),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x7.png")))).InSequence(s4);
+
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(lowest),
+ Contains(Pair(mod_spdy::spdy::kSpdy3Path, "/x8.png")))).InSequence(s5);
+
+ // Add multiple X-Associated-Content headers to both headers_out and
+ // err_headers_out.
+ apr_table_addn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"/x1.png\":2, \"/x2.png\":0");
+ apr_table_addn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"/x3.png\"");
+ apr_table_addn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"/x4.png\":3, \"/x5.png\", \"/x6.png\":1");
+ apr_table_addn(request_->err_headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"/x7.png\" : 2");
+ apr_table_addn(request_->err_headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"/x8.png\"");
+
+ WriteBrigade(&server_push_filter);
+ // All the X-Associated-Content headers should get removed.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+ EXPECT_TRUE(apr_table_get(request_->err_headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+// Test that header key matching is case-insensitive.
+TEST_P(ServerPushFilterTest, CaseInsensitive) {
+ const net::SpdyStreamId stream_id = 13;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ EXPECT_CALL(pusher_, StartServerPush(Eq(stream_id), _, _, Contains(
+ Pair(mod_spdy::spdy::kSpdy3Path, "/x1.png"))));
+ EXPECT_CALL(pusher_, StartServerPush(Eq(stream_id), _, _, Contains(
+ Pair(mod_spdy::spdy::kSpdy3Path, "/x2.png"))));
+ EXPECT_CALL(pusher_, StartServerPush(Eq(stream_id), _, _, Contains(
+ Pair(mod_spdy::spdy::kSpdy3Path, "/x3.png"))));
+
+ apr_table_addn(request_->headers_out, "X-Associated-Content", "\"/x1.png\"");
+ apr_table_addn(request_->headers_out, "X-ASSOCIATED-CONTENT", "\"/x2.png\"");
+ apr_table_addn(request_->headers_out, "x-AsSoCiAtEd-cOnTeNt", "\"/x3.png\"");
+
+ WriteBrigade(&server_push_filter);
+ // All three X-Associated-Content headers should get removed, despite their
+ // weird capitalization. (Note that apr_table_get is itself
+ // case-insensitive, but we explicitly check all the variations here anyway,
+ // just to be sure.)
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ "X-Associated-Content") == NULL);
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ "X-ASSOCIATED-CONTENT") == NULL);
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ "x-AsSoCiAtEd-cOnTeNt") == NULL);
+}
+
+TEST_P(ServerPushFilterTest, CopyApplicableHeaders) {
+ const net::SpdyStreamId stream_id = 7;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 0;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ // Set some extra headers on the original request (which was evidentally a
+ // POST). The Accept-Language header should get copied over for the push,
+ // but the Content-Length header obviously should not.
+ apr_table_setn(request_->headers_in, "accept-language", "en-US");
+ apr_table_setn(request_->headers_in, "content-length", "200");
+
+ net::SpdyHeaderBlock headers1;
+ headers1[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ headers1[mod_spdy::spdy::kSpdy3Method] = "GET";
+ headers1[mod_spdy::spdy::kSpdy3Path] = "/foo/bar.css";
+ headers1[mod_spdy::spdy::kSpdy3Scheme] = "https";
+ headers1[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ headers1[mod_spdy::http::kReferer] = kRefererUrl;
+ headers1["accept-language"] = "en-US";
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(2u), Eq(headers1)));
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ " \"https://www.example.com/foo/bar.css\" : 2 ");
+ WriteBrigade(&server_push_filter);
+}
+
+TEST_P(ServerPushFilterTest, StopPushingAfterPushError) {
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ // When the filter tries to push the first resource, we reply that pushes are
+ // no longer possible on this connection. The filter should not attempt any
+ // more pushes, even though more were specified.
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id),
+ Eq(initial_server_push_depth + 1),
+ Eq(2u), _)).WillOnce(
+ Return(mod_spdy::SpdyServerPushInterface::CANNOT_PUSH_EVER_AGAIN));
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"https://www.example.com/foo/bar.css?q=12\":2,"
+ "\"cdn.example.com:8080/images/foo.png\","
+ "\"/scripts/awesome.js\":0");
+ WriteBrigade(&server_push_filter);
+ // The X-Associated-Content header should still get removed, though.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+TEST_P(ServerPushFilterTest, StopPushingAfterParseError) {
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ // The filter should push the first resource, but then stop when it gets to
+ // the parse error.
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(2u), _));
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"https://www.example.com/foo/bar.css?q=12\":2,"
+ "oops.iforgot.to/quote/this/url.js,"
+ "\"/scripts/awesome.js\":0");
+ WriteBrigade(&server_push_filter);
+ // The X-Associated-Content header should still get removed, though.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+TEST_P(ServerPushFilterTest, SkipInvalidQuotedUrl) {
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ // The filter should push the first and third resources, but skip the second
+ // one because its quoted URL is invalid.
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(2u), _));
+ EXPECT_CALL(pusher_, StartServerPush(
+ Eq(stream_id), Eq(initial_server_push_depth + 1), Eq(0u), _));
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ " \"https://www.example.com/foo/bar.css?q=12\" : 2, "
+ "\"https://this.is:not/a valid URL!\":1, "
+ "\"/scripts/awesome.js\":0 ");
+ WriteBrigade(&server_push_filter);
+ // The X-Associated-Content header should still get removed, though.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+TEST_P(ServerPushFilterTest, MaxServerPushDepthLimit) {
+ const net::SpdyStreamId stream_id = 2;
+ const net::SpdyStreamId associated_stream_id = 5;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ mod_spdy::SpdyServerConfig server_cfg;
+
+ server_cfg.set_max_server_push_depth(0);
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ // We should not get any calls to StartServerPush, because we do not allow
+ // server-pushed resources to push any more resources.
+ EXPECT_CALL(pusher_, StartServerPush(_,_,_,_)).Times(0);
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"https://www.example.com/foo/bar.css?q=12\":2,"
+ "\"cdn.example.com:8080/images/foo.png\","
+ "\"/scripts/awesome.js\":0");
+ WriteBrigade(&server_push_filter);
+ // The X-Associated-Content header should still get removed, though.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+
+
+ // Now increase our max_server_push_depth, but also our
+ // initial_server_push_depth. We expect the same result.
+ const int32 initial_server_push_depth_2 = 5;
+ mod_spdy::SpdyServerConfig server_cfg_2;
+ server_cfg.set_max_server_push_depth(5);
+ mod_spdy::SpdyStream stream_2(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth_2, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter_2(
+ &stream_2, request_, &server_cfg_2);
+
+ // We should not get any calls to StartServerPush, because we do not allow
+ // server-pushed resources to push any more resources.
+ EXPECT_CALL(pusher_, StartServerPush(_,_,_,_)).Times(0);
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"https://www.example.com/foo/bar.css?q=12\":2,"
+ "\"cdn.example.com:8080/images/foo.png\","
+ "\"/scripts/awesome.js\":0");
+ WriteBrigade(&server_push_filter_2);
+ // The X-Associated-mContent header should still get removed, though.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+// Run server push tests only over SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy3, ServerPushFilterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_3, mod_spdy::spdy::SPDY_VERSION_3_1));
+
+// Create a type alias so that we can instantiate some of our
+// SpdySessionTest-based tests using a different set of parameters.
+typedef ServerPushFilterTest ServerPushFilterSpdy2Test;
+
+TEST_P(ServerPushFilterSpdy2Test, NoPushesForSpdy2) {
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyStreamId associated_stream_id = 0;
+ const int32 initial_server_push_depth = 0;
+ const net::SpdyPriority priority = 1;
+ const mod_spdy::SpdyServerConfig server_cfg;
+ mod_spdy::SpdyStream stream(
+ spdy_version_, stream_id, associated_stream_id,
+ initial_server_push_depth, priority, net::kSpdyStreamInitialWindowSize,
+ &output_queue_, &shared_window_, &pusher_);
+ mod_spdy::ServerPushFilter server_push_filter(&stream, request_, &server_cfg);
+
+ // We should not get any calls to StartServerPush when we're on SPDY/2.
+
+ apr_table_setn(request_->headers_out, mod_spdy::http::kXAssociatedContent,
+ "\"https://www.example.com/foo/bar.css?q=12\":2,"
+ "\"cdn.example.com:8080/images/foo.png\","
+ "\"/scripts/awesome.js\":0");
+ WriteBrigade(&server_push_filter);
+ // The X-Associated-Content header should still get removed, though.
+ EXPECT_TRUE(apr_table_get(request_->headers_out,
+ mod_spdy::http::kXAssociatedContent) == NULL);
+}
+
+INSTANTIATE_TEST_CASE_P(Spdy2, ServerPushFilterSpdy2Test, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2));
+
+} // namespace
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/filters/spdy_to_http_filter.h"
+
+#include <map>
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/spdy_to_http_converter.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// If, during an AP_MODE_GETLINE read, we pull in this much data (or more)
+// without seeing a linebreak, just give up and return what we have.
+const size_t kGetlineThreshold = 4096;
+
+} // namespace
+
+namespace mod_spdy {
+
+SpdyToHttpFilter::SpdyToHttpFilter(SpdyStream* stream)
+ : stream_(stream),
+ visitor_(&data_buffer_),
+ converter_(stream_->spdy_version(), &visitor_),
+ next_read_start_(0) {
+ DCHECK(stream_ != NULL);
+}
+
+SpdyToHttpFilter::~SpdyToHttpFilter() {}
+
+// Macro to check if the SPDY stream has been aborted; if so, mark the
+// connection object as having been aborted and return APR_ECONNABORTED.
+// Hopefully, this will convince Apache to shut down processing for this
+// (slave) connection, thus allowing this stream's thread to complete and exit.
+//
+// As an extra measure, we also insert an EOS bucket into the brigade before
+// returning. This idea comes from ssl_io_filter_input() in ssl_engine_io.c in
+// mod_ssl, which does so with the following comment: "Ok, if we aborted, we
+// ARE at the EOS. We also have aborted. This 'double protection' is probably
+// redundant, but also effective against just about anything."
+#define RETURN_IF_STREAM_ABORT(filter, brigade) \
+ do { \
+ if ((filter)->c->aborted || stream_->is_aborted()) { \
+ (filter)->c->aborted = true; \
+ APR_BRIGADE_INSERT_TAIL( \
+ (brigade), apr_bucket_eos_create((filter)->c->bucket_alloc)); \
+ return APR_ECONNABORTED; \
+ } \
+ } while (false)
+
+apr_status_t SpdyToHttpFilter::Read(ap_filter_t *filter,
+ apr_bucket_brigade *brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes) {
+ // Turn readbytes into a size_t to avoid the need for static_casts below. To
+ // avoid any surprises (in cases where apr_off_t is signed), clamp it to a
+ // non-negative value first.
+ const size_t max_bytes = std::max(static_cast<apr_off_t>(0), readbytes);
+
+ // This is a NETWORK-level filter, so there shouldn't be any filter after us.
+ if (filter->next != NULL) {
+ LOG(WARNING) << "SpdyToHttpFilter is not the last filter in the chain "
+ << "(it is followed by " << filter->next->frec->name << ")";
+ }
+
+ // Clear any buffer data that was already returned on a previous invocation
+ // of this filter.
+ if (next_read_start_ > 0) {
+ data_buffer_.erase(0, next_read_start_);
+ next_read_start_ = 0;
+ }
+
+ // We don't need to do anything for AP_MODE_INIT. (We check this case before
+ // checking for EOF, becuase that's what ap_core_input_filter() in
+ // core_filters.c does.)
+ if (mode == AP_MODE_INIT) {
+ return APR_SUCCESS;
+ }
+
+ // If there will never be any more data on this stream, return EOF. (That's
+ // what ap_core_input_filter() in core_filters.c does.)
+ if (end_of_stream_reached() && data_buffer_.empty()) {
+ return APR_EOF;
+ }
+
+ // Check if this SPDY stream has been aborted, and if so, quit. We will also
+ // check for aborts just after each time we call GetNextFrame (that's a good
+ // time to check, since a stream abort can interrupt a blocking call to
+ // GetNextFrame).
+ RETURN_IF_STREAM_ABORT(filter, brigade);
+
+ // Keep track of how much data, if any, we should place into the brigade.
+ size_t bytes_read = 0;
+
+ // For AP_MODE_READBYTES and AP_MODE_SPECULATIVE, we try to read the quantity
+ // of bytes we are asked for. For AP_MODE_EXHAUSTIVE, we read as much as
+ // possible.
+ if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE ||
+ mode == AP_MODE_EXHAUSTIVE) {
+ // Try to get as much data as we were asked for.
+ while (max_bytes > data_buffer_.size() || mode == AP_MODE_EXHAUSTIVE) {
+ const bool got_frame = GetNextFrame(block);
+ RETURN_IF_STREAM_ABORT(filter, brigade);
+ if (!got_frame) {
+ break;
+ }
+ }
+
+ // Return however much data we read, but no more than they asked for.
+ bytes_read = data_buffer_.size();
+ if (mode != AP_MODE_EXHAUSTIVE && max_bytes < bytes_read) {
+ bytes_read = max_bytes;
+ }
+ }
+ // For AP_MODE_GETLINE, try to return a full text line of data.
+ else if (mode == AP_MODE_GETLINE) {
+ // Try to find the first linebreak in the remaining data stream.
+ size_t linebreak = std::string::npos;
+ size_t start = 0;
+ while (true) {
+ linebreak = data_buffer_.find('\n', start);
+ // Stop if we find a linebreak, or if we've pulled too much data already.
+ if (linebreak != std::string::npos ||
+ data_buffer_.size() >= kGetlineThreshold) {
+ break;
+ }
+ // Remember where we left off so we don't have to re-scan the whole
+ // buffer on the next iteration.
+ start = data_buffer_.size();
+ // We haven't seen a linebreak yet, so try to get more data.
+ const bool got_frame = GetNextFrame(block);
+ RETURN_IF_STREAM_ABORT(filter, brigade);
+ if (!got_frame) {
+ break;
+ }
+ }
+
+ // If we found a linebreak, return data up to and including that linebreak.
+ // Otherwise, just send whatever we were able to get.
+ bytes_read = (linebreak == std::string::npos ?
+ data_buffer_.size() : linebreak + 1);
+ }
+ // We don't support AP_MODE_EATCRLF. Doing so would be tricky, and probably
+ // totally pointless. But if we ever decide to implement it, see
+ // http://mail-archives.apache.org/mod_mbox/httpd-dev/200504.mbox/%3C1e86e5df78f13fcc9af02b3f5d749b33@ricilake.net%3E
+ // for more information on its subtle semantics.
+ else {
+ DCHECK(mode == AP_MODE_EATCRLF);
+ VLOG(2) << "Unsupported read mode (" << mode << ") on stream "
+ << stream_->stream_id();
+ return APR_ENOTIMPL;
+ }
+
+ // Keep track of whether we were able to put any buckets into the brigade.
+ bool success = false;
+
+ // If we managed to read any data, put it into the brigade. We use a
+ // transient bucket (as opposed to a heap bucket) to avoid an extra string
+ // copy.
+ if (bytes_read > 0) {
+ APR_BRIGADE_INSERT_TAIL(brigade, apr_bucket_transient_create(
+ data_buffer_.data(), bytes_read, brigade->bucket_alloc));
+ success = true;
+ }
+
+ // If this is the last bit of data from this stream, send an EOS bucket.
+ if (end_of_stream_reached() && bytes_read == data_buffer_.size()) {
+ APR_BRIGADE_INSERT_TAIL(brigade, apr_bucket_eos_create(
+ brigade->bucket_alloc));
+ success = true;
+ }
+
+ // If this read failed and this was a non-blocking read, invite the caller to
+ // try again.
+ if (!success && block == APR_NONBLOCK_READ) {
+ return APR_EAGAIN;
+ }
+
+ // Unless this is a speculative read, we should skip past the bytes we read
+ // next time this filter is invoked. We don't want to erase those bytes
+ // yet, though, so that we can return them to the previous filter in a
+ // transient bucket.
+ if (mode != AP_MODE_SPECULATIVE) {
+ next_read_start_ = bytes_read;
+ }
+
+ return APR_SUCCESS;
+}
+
+SpdyToHttpFilter::DecodeFrameVisitor::DecodeFrameVisitor(
+ SpdyToHttpFilter* filter)
+ : filter_(filter), success_(false) {
+ DCHECK(filter_);
+}
+
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitSynStream(
+ const net::SpdySynStreamIR& frame) {
+ success_ = filter_->DecodeSynStreamFrame(frame);
+}
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitSynReply(
+ const net::SpdySynReplyIR& frame) { BadFrameType("SYN_REPLY"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitRstStream(
+ const net::SpdyRstStreamIR& frame) { BadFrameType("RST_STREAM"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitSettings(
+ const net::SpdySettingsIR& frame) { BadFrameType("SETTINGS"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitPing(
+ const net::SpdyPingIR& frame) { BadFrameType("PING"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitGoAway(
+ const net::SpdyGoAwayIR& frame) { BadFrameType("GOAWAY"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitHeaders(
+ const net::SpdyHeadersIR& frame) {
+ success_ = filter_->DecodeHeadersFrame(frame);
+}
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitWindowUpdate(
+ const net::SpdyWindowUpdateIR& frame) { BadFrameType("WINDOW_UPDATE"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitCredential(
+ const net::SpdyCredentialIR& frame) { BadFrameType("CREDENTIAL"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitBlocked(
+ const net::SpdyBlockedIR& frame) { BadFrameType("BLOCKED"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitPushPromise(
+ const net::SpdyPushPromiseIR& frame) { BadFrameType("PUSH_PROMISE"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitData(
+ const net::SpdyDataIR& frame) {
+ success_ = filter_->DecodeDataFrame(frame);
+}
+
+void SpdyToHttpFilter::DecodeFrameVisitor::BadFrameType(
+ const char* frame_type) {
+ LOG(DFATAL) << "Master connection sent a " << frame_type
+ << " frame to stream " << filter_->stream_->stream_id();
+ filter_->AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ success_ = false;
+}
+
+bool SpdyToHttpFilter::GetNextFrame(apr_read_type_e block) {
+ if (end_of_stream_reached()) {
+ return false;
+ }
+
+ // Try to get the next SPDY frame from the stream.
+ scoped_ptr<net::SpdyFrameIR> frame;
+ {
+ net::SpdyFrameIR* frame_ptr = NULL;
+ if (!stream_->GetInputFrame(block == APR_BLOCK_READ, &frame_ptr)) {
+ DCHECK(frame_ptr == NULL);
+ return false;
+ }
+ frame.reset(frame_ptr);
+ }
+ DCHECK(frame.get() != NULL);
+
+ // Decode the frame into HTTP and append to the data buffer.
+ DecodeFrameVisitor visitor(this);
+ frame->Visit(&visitor);
+ return visitor.success();
+}
+
+bool SpdyToHttpFilter::DecodeSynStreamFrame(
+ const net::SpdySynStreamIR& frame) {
+ const SpdyToHttpConverter::Status status =
+ converter_.ConvertSynStreamFrame(frame);
+ switch (status) {
+ case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+ return true;
+ case SpdyToHttpConverter::EXTRA_SYN_STREAM:
+ // If we get multiple SYN_STREAM frames for a stream, we must abort
+ // with PROTOCOL_ERROR (SPDY draft 2 section 2.7.1).
+ LOG(ERROR) << "Client sent extra SYN_STREAM frame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_PROTOCOL_ERROR);
+ return false;
+ case SpdyToHttpConverter::INVALID_HEADER_BLOCK:
+ LOG(ERROR) << "Invalid SYN_STREAM header block on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_PROTOCOL_ERROR);
+ return false;
+ case SpdyToHttpConverter::BAD_REQUEST:
+ // TODO(mdsteeele): According to the SPDY spec, we're supposed to return
+ // an HTTP 400 (Bad Request) reply in this case (SPDY draft 3 section
+ // 3.2.1). We need to do some refactoring to make that possible.
+ LOG(ERROR) << "Could not generate request line from SYN_STREAM frame"
+ << " in stream " << stream_->stream_id();
+ AbortStream(net::RST_STREAM_REFUSED_STREAM);
+ return false;
+ default:
+ // No other outcome should be possible.
+ LOG(DFATAL) << "Got " << SpdyToHttpConverter::StatusString(status)
+ << " from ConvertSynStreamFrame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ return false;
+ }
+}
+
+bool SpdyToHttpFilter::DecodeHeadersFrame(const net::SpdyHeadersIR& frame) {
+ const SpdyToHttpConverter::Status status =
+ converter_.ConvertHeadersFrame(frame);
+ switch (status) {
+ case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+ return true;
+ case SpdyToHttpConverter::FRAME_AFTER_FIN:
+ AbortStream(net::RST_STREAM_INVALID_STREAM);
+ return false;
+ case SpdyToHttpConverter::INVALID_HEADER_BLOCK:
+ LOG(ERROR) << "Invalid HEADERS header block on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_PROTOCOL_ERROR);
+ return false;
+ default:
+ // No other outcome should be possible.
+ LOG(DFATAL) << "Got " << SpdyToHttpConverter::StatusString(status)
+ << " from ConvertHeadersFrame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ return false;
+ }
+}
+
+bool SpdyToHttpFilter::DecodeDataFrame(const net::SpdyDataIR& frame) {
+ const SpdyToHttpConverter::Status status =
+ converter_.ConvertDataFrame(frame);
+ switch (status) {
+ case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+ // TODO(mdsteele): This isn't really the ideal place for this -- we
+ // shouldn't send the WINDOW_UPDATE until we're about to return the
+ // data to the previous filter, so that we're aren't buffering an
+ // unbounded amount of data in this filter. The trouble is that once
+ // we convert the frames, everything goes into data_buffer_ and we
+ // forget which of it is leading/trailing headers and which of it is
+ // request data, so it'll take a little work to know when to send the
+ // WINDOW_UPDATE frames. For now, just doing it here is good enough.
+ stream_->OnInputDataConsumed(frame.data().size());
+ return true;
+ case SpdyToHttpConverter::FRAME_AFTER_FIN:
+ // If the stream is no longer open, we must send a RST_STREAM with
+ // INVALID_STREAM (SPDY draft 3 section 2.2.2).
+ AbortStream(net::RST_STREAM_INVALID_STREAM);
+ return false;
+ default:
+ // No other outcome should be possible.
+ LOG(DFATAL) << "Got " << SpdyToHttpConverter::StatusString(status)
+ << " from ConvertDataFrame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ return false;
+ }
+}
+
+void SpdyToHttpFilter::AbortStream(net::SpdyRstStreamStatus status) {
+ stream_->AbortWithRstStream(status);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_APACHE_FILTERS_SPDY_TO_HTTP_FILTER_H_
+#define MOD_SPDY_APACHE_FILTERS_SPDY_TO_HTTP_FILTER_H_
+
+#include <string>
+
+#include "apr_buckets.h"
+#include "util_filter.h"
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/http_string_builder.h"
+#include "mod_spdy/common/spdy_to_http_converter.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// An Apache filter for pulling SPDY frames (for a single SPDY stream) from the
+// input queue of a SpdyStream object and converting them into equivalent HTTP
+// data to be processed by Apache. This is intended to be the outermost filter
+// in the input chain of one of our slave connections, essentially taking the
+// place of the network socket.
+class SpdyToHttpFilter {
+ public:
+ explicit SpdyToHttpFilter(SpdyStream* stream);
+ ~SpdyToHttpFilter();
+
+ apr_status_t Read(ap_filter_t* filter,
+ apr_bucket_brigade* brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes);
+
+ private:
+ friend class DecodeFrameVisitor;
+ class DecodeFrameVisitor : public net::SpdyFrameVisitor {
+ public:
+ explicit DecodeFrameVisitor(SpdyToHttpFilter* filter);
+ virtual ~DecodeFrameVisitor() {}
+
+ bool success() { return success_; }
+
+ virtual void VisitSynStream(const net::SpdySynStreamIR& frame);
+ virtual void VisitSynReply(const net::SpdySynReplyIR& frame);
+ virtual void VisitRstStream(const net::SpdyRstStreamIR& frame);
+ virtual void VisitSettings(const net::SpdySettingsIR& frame);
+ virtual void VisitPing(const net::SpdyPingIR& frame);
+ virtual void VisitGoAway(const net::SpdyGoAwayIR& frame);
+ virtual void VisitHeaders(const net::SpdyHeadersIR& frame);
+ virtual void VisitWindowUpdate(const net::SpdyWindowUpdateIR& frame);
+ virtual void VisitCredential(const net::SpdyCredentialIR& frame);
+ virtual void VisitBlocked(const net::SpdyBlockedIR& frame);
+ virtual void VisitPushPromise(const net::SpdyPushPromiseIR& frame);
+ virtual void VisitData(const net::SpdyDataIR& frame);
+
+ private:
+ void BadFrameType(const char* frame_type);
+
+ SpdyToHttpFilter* filter_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecodeFrameVisitor);
+ };
+
+ // Return true if we've received a FLAG_FIN (i.e. EOS has been reached).
+ bool end_of_stream_reached() const { return visitor_.is_complete(); }
+
+ // Try to get the next SPDY frame on this stream, convert it into HTTP, and
+ // append the resulting data to data_buffer_. If the block argument is
+ // APR_BLOCK_READ, this function will block until a frame comes in (or the
+ // stream is closed).
+ bool GetNextFrame(apr_read_type_e block);
+
+ // Pass the given frame to the SpdyToHttpConverter, and deal with the return
+ // code appropriately.
+ bool DecodeSynStreamFrame(const net::SpdySynStreamIR& frame);
+ bool DecodeHeadersFrame(const net::SpdyHeadersIR& frame);
+ bool DecodeDataFrame(const net::SpdyDataIR& frame);
+
+ // Send a RST_STREAM frame and abort the stream.
+ void AbortStream(net::SpdyRstStreamStatus status);
+
+ SpdyStream* const stream_;
+ std::string data_buffer_;
+ HttpStringBuilder visitor_;
+ SpdyToHttpConverter converter_;
+ size_t next_read_start_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyToHttpFilter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_FILTERS_SPDY_TO_HTTP_FILTER_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/filters/spdy_to_http_filter.h"
+
+#include <string>
+
+#include "httpd.h"
+#include "apr_buckets.h"
+#include "apr_tables.h"
+#include "util_filter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+ MOCK_METHOD4(StartServerPush,
+ mod_spdy::SpdyServerPushInterface::PushStatus(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyNameValueBlock& request_headers));
+};
+
+class SpdyToHttpFilterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ SpdyToHttpFilterTest()
+ : spdy_version_(GetParam()),
+ stream_id_(1),
+ priority_(0u),
+ shared_window_(net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize),
+ stream_(spdy_version_, stream_id_, 0, 0, priority_,
+ net::kSpdyStreamInitialWindowSize, &output_queue_,
+ &shared_window_, &pusher_),
+ spdy_to_http_filter_(&stream_) {
+ bucket_alloc_ = apr_bucket_alloc_create(local_.pool());
+ connection_ = static_cast<conn_rec*>(
+ apr_pcalloc(local_.pool(), sizeof(conn_rec)));
+ connection_->pool = local_.pool();
+ connection_->bucket_alloc = bucket_alloc_;
+ ap_filter_ = static_cast<ap_filter_t*>(
+ apr_pcalloc(local_.pool(), sizeof(ap_filter_t)));
+ ap_filter_->c = connection_;
+ brigade_ = apr_brigade_create(local_.pool(), bucket_alloc_);
+ }
+
+ protected:
+ void PostSynStreamFrame(bool fin, const net::SpdyNameValueBlock& headers) {
+ scoped_ptr<net::SpdySynStreamIR> frame(
+ new net::SpdySynStreamIR(stream_id_));
+ frame->set_priority(priority_);
+ frame->set_fin(fin);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ stream_.PostInputFrame(frame.release());
+ }
+
+ void PostHeadersFrame(bool fin, const net::SpdyNameValueBlock& headers) {
+ scoped_ptr<net::SpdyHeadersIR> frame(new net::SpdyHeadersIR(stream_id_));
+ frame->set_fin(fin);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ stream_.PostInputFrame(frame.release());
+ }
+
+ void PostDataFrame(bool fin, const base::StringPiece& payload) {
+ scoped_ptr<net::SpdyDataIR> frame(
+ new net::SpdyDataIR(stream_id_, payload));
+ frame->set_fin(fin);
+ EXPECT_TRUE(shared_window_.OnReceiveInputData(payload.size()));
+ stream_.PostInputFrame(frame.release());
+ }
+
+ apr_status_t Read(ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t readbytes) {
+ return spdy_to_http_filter_.Read(ap_filter_, brigade_,
+ mode, block, readbytes);
+ }
+
+ void ExpectTransientBucket(const std::string& expected) {
+ ASSERT_FALSE(APR_BRIGADE_EMPTY(brigade_))
+ << "Expected TRANSIENT bucket, but brigade is empty.";
+ apr_bucket* bucket = APR_BRIGADE_FIRST(brigade_);
+ ASSERT_TRUE(APR_BUCKET_IS_TRANSIENT(bucket))
+ << "Expected TRANSIENT bucket, but found " << bucket->type->name
+ << " bucket.";
+ const char* data = NULL;
+ apr_size_t size = 0;
+ ASSERT_EQ(APR_SUCCESS, apr_bucket_read(
+ bucket, &data, &size, APR_NONBLOCK_READ));
+ EXPECT_EQ(expected, std::string(data, size));
+ apr_bucket_delete(bucket);
+ }
+
+ void ExpectEosBucket() {
+ ASSERT_FALSE(APR_BRIGADE_EMPTY(brigade_))
+ << "Expected EOS bucket, but brigade is empty.";
+ apr_bucket* bucket = APR_BRIGADE_FIRST(brigade_);
+ ASSERT_TRUE(APR_BUCKET_IS_EOS(bucket))
+ << "Expected EOS bucket, but found " << bucket->type->name
+ << " bucket.";
+ apr_bucket_delete(bucket);
+ }
+
+ void ExpectEndOfBrigade() {
+ ASSERT_TRUE(APR_BRIGADE_EMPTY(brigade_))
+ << "Expected brigade to be empty, but found "
+ << APR_BRIGADE_FIRST(brigade_)->type->name << " bucket.";
+ ASSERT_EQ(APR_SUCCESS, apr_brigade_cleanup(brigade_));
+ }
+
+ void ExpectRstStream(net::SpdyRstStreamStatus status) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue_.Pop(&raw_frame))
+ << "Expected RST_STREAM frame, but output queue is empty.";
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, mod_spdy::testing::IsRstStream(stream_id_, status));
+ }
+
+ void ExpectNoMoreOutputFrames() {
+ EXPECT_TRUE(output_queue_.IsEmpty());
+ }
+
+ bool is_spdy2() const { return GetParam() < mod_spdy::spdy::SPDY_VERSION_3; }
+
+ const char* host_header_name() const {
+ return is_spdy2() ? mod_spdy::http::kHost : mod_spdy::spdy::kSpdy3Host;
+ }
+ const char* method_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Method :
+ mod_spdy::spdy::kSpdy3Method);
+ }
+ const char* path_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Url :
+ mod_spdy::spdy::kSpdy3Path);
+ }
+ const char* scheme_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Scheme :
+ mod_spdy::spdy::kSpdy3Scheme);
+ }
+ const char* version_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Version :
+ mod_spdy::spdy::kSpdy3Version);
+ }
+
+ const mod_spdy::spdy::SpdyVersion spdy_version_;
+ const net::SpdyStreamId stream_id_;
+ const net::SpdyPriority priority_;
+ mod_spdy::SpdyFramePriorityQueue output_queue_;
+ mod_spdy::SharedFlowControlWindow shared_window_;
+ MockSpdyServerPushInterface pusher_;
+ mod_spdy::SpdyStream stream_;
+ mod_spdy::SpdyToHttpFilter spdy_to_http_filter_;
+
+ mod_spdy::LocalPool local_;
+ apr_bucket_alloc_t* bucket_alloc_;
+ conn_rec* connection_;
+ ap_filter_t* ap_filter_;
+ apr_bucket_brigade* brigade_;
+};
+
+TEST_P(SpdyToHttpFilterTest, SimpleGetRequest) {
+ // Perform an INIT. It should succeed, with no effect.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_INIT, APR_BLOCK_READ, 1337));
+ ExpectEndOfBrigade();
+
+ // Invoke the fitler in non-blocking GETLINE mode. We shouldn't get anything
+ // yet, because we haven't sent any frames from the client yet.
+ ASSERT_TRUE(APR_STATUS_IS_EAGAIN(
+ Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0)));
+ ExpectEndOfBrigade();
+
+ // Send a SYN_STREAM frame from the client, with FLAG_FIN set.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.com";
+ headers[method_header_name()] = "GET";
+ headers["referer"] = "https://www.example.com/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/foo/bar/index.html";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ headers["x-do-not-track"] = "1";
+ PostSynStreamFrame(true, headers);
+
+ // Invoke the filter in blocking GETLINE mode. We should get back just the
+ // HTTP request line.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_BLOCK_READ, 0));
+ ExpectTransientBucket("GET /foo/bar/index.html HTTP/1.1\r\n");
+ ExpectEndOfBrigade();
+
+ // Now do a SPECULATIVE read. We should get back a few bytes.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_SPECULATIVE, APR_NONBLOCK_READ, 8));
+ ExpectTransientBucket("host: ww");
+ ExpectEndOfBrigade();
+
+ // Now do another GETLINE read. We should get back the first header line,
+ // including the data we just read speculatively.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("host: www.example.com\r\n");
+ ExpectEndOfBrigade();
+
+ // Do a READBYTES read. We should get back a few bytes.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 12));
+ ExpectTransientBucket("referer: htt");
+ ExpectEndOfBrigade();
+
+ // Do another GETLINE read. We should get back the rest of the header line,
+ // *not* including the data we just read.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("ps://www.example.com/index.html\r\n");
+ ExpectEndOfBrigade();
+
+ // Finally, do an EXHAUSTIVE read. We should get back everything that
+ // remains, terminating with an EOS bucket.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("user-agent: ModSpdyUnitTest/1.0\r\n"
+ "x-do-not-track: 1\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+
+ // There's no more data left; attempting another read should result in EOF.
+ ASSERT_TRUE(APR_STATUS_IS_EOF(
+ Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 4)));
+}
+
+TEST_P(SpdyToHttpFilterTest, SimplePostRequest) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.com";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.com/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/erase/the/whole/database.cgi";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Do a nonblocking READBYTES read. We ask for lots of bytes, but since it's
+ // nonblocking we should immediately get back what's available so far.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 4096));
+ ExpectTransientBucket("POST /erase/the/whole/database.cgi HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "referer: https://www.example.com/index.html\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n");
+ ExpectEndOfBrigade();
+
+ // There's nothing more available yet, so a nonblocking read should fail.
+ ASSERT_TRUE(APR_STATUS_IS_EAGAIN(
+ Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 4)));
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+
+ // Send some DATA frames.
+ PostDataFrame(false, "Hello, world!\nPlease erase ");
+ PostDataFrame(false, "the whole database ");
+ PostDataFrame(true, "immediately.\nThanks!\n");
+
+ // Now read in the data a bit at a time.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("transfer-encoding: chunked\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("accept-encoding: gzip,deflate\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("1B\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 24));
+ ExpectTransientBucket("Hello, world!\nPlease era");
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_SPECULATIVE, APR_NONBLOCK_READ, 15));
+ ExpectTransientBucket("se \r\n13\r\nthe wh");
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 36));
+ ExpectTransientBucket("se \r\n13\r\nthe whole database \r\n15\r\nim");
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 21));
+ ExpectTransientBucket("mediately.\nThanks!\n\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("0\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+
+ // There's no more data left; attempting another read should result in EOF.
+ ASSERT_TRUE(APR_STATUS_IS_EOF(Read(AP_MODE_GETLINE, APR_BLOCK_READ, 0)));
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithHeadersFrames) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.net";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.net/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/erase/the/whole/database.cgi";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Send some DATA and HEADERS frames. The HEADERS frames should get buffered
+ // and placed at the end of the HTTP request body as trailing headers.
+ PostDataFrame(false, "Please erase ");
+ net::SpdyNameValueBlock headers2;
+ headers2["x-super-cool"] = "foo";
+ PostHeadersFrame(false, headers2);
+ PostDataFrame(false, "everything ");
+ net::SpdyNameValueBlock headers3;
+ headers3["x-awesome"] = "quux";
+ PostHeadersFrame(false, headers3);
+ PostDataFrame(true, "immediately!!\n");
+
+ // Read in all the data.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /erase/the/whole/database.cgi HTTP/1.1\r\n"
+ "host: www.example.net\r\n"
+ "referer: https://www.example.net/index.html\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "D\r\n"
+ "Please erase \r\n"
+ "B\r\n"
+ "everything \r\n"
+ "E\r\n"
+ "immediately!!\n\r\n"
+ "0\r\n"
+ "x-awesome: quux\r\n"
+ "x-super-cool: foo\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, GetRequestWithHeadersRightAfterSynStream) {
+ // Send a SYN_STREAM frame with some of the headers.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "GET";
+ headers["referer"] = "https://www.example.org/foo/bar.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/index.html";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Read in everything that's available so far.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("GET /index.html HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/foo/bar.html\r\n");
+ ExpectEndOfBrigade();
+
+ // Send a HEADERS frame with the rest of the headers.
+ net::SpdyNameValueBlock headers2;
+ headers2["accept-encoding"] = "deflate, gzip";
+ headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+ PostHeadersFrame(true, headers2);
+
+ // Read in the rest of the request.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("accept-encoding: deflate, gzip\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithHeadersRightAfterSynStream) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/delete/everything.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ headers["x-zzzz"] = "4Z";
+ PostSynStreamFrame(false, headers);
+
+ // Send a HEADERS frame before sending any data frames.
+ net::SpdyNameValueBlock headers2;
+ headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+ PostHeadersFrame(false, headers2);
+
+ // Now send a couple DATA frames and a final HEADERS frame.
+ PostDataFrame(false, "Please erase everything immediately");
+ PostDataFrame(false, ", thanks!\n");
+ net::SpdyNameValueBlock headers3;
+ headers3["x-qqq"] = "3Q";
+ PostHeadersFrame(true, headers3);
+
+ // Read in all the data. The first HEADERS frame should get put in before
+ // the data, and the last HEADERS frame should get put in after the data.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /delete/everything.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "x-zzzz: 4Z\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "23\r\n"
+ "Please erase everything immediately\r\n"
+ "A\r\n"
+ ", thanks!\n\r\n"
+ "0\r\n"
+ "x-qqq: 3Q\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithEmptyDataFrameInMiddle) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Now send a few DATA frames, with a zero-length data frame in the middle.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(false, "");
+ PostDataFrame(true, "stuff.\n");
+
+ // Read in all the data. The empty data frame should be ignored.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "9\r\n"
+ "Please do\r\n"
+ "6\r\n"
+ " some \r\n"
+ "7\r\n"
+ "stuff.\n\r\n"
+ "0\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithEmptyDataFrameAtEnd) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Now send a few DATA frames, with a zero-length data frame at the end.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(false, "stuff.\n");
+ PostDataFrame(true, "");
+
+ // Read in all the data. The empty data frame should be ignored (except for
+ // its FLAG_FIN).
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "9\r\n"
+ "Please do\r\n"
+ "6\r\n"
+ " some \r\n"
+ "7\r\n"
+ "stuff.\n\r\n"
+ "0\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithContentLength) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Send a few more headers before sending data, including a content-length.
+ net::SpdyNameValueBlock headers2;
+ headers2["content-length"] = "22";
+ headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+ PostHeadersFrame(false, headers2);
+
+ // Now send a few DATA frames.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(true, "stuff.\n");
+
+ // Read in all the data. Because we supplied a content-length, chunked
+ // encoding should not be used (to support modules that don't work with
+ // chunked requests).
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "content-length: 22\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "Please do some stuff.\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithContentLengthAndTrailingHeaders) {
+ // Send a SYN_STREAM frame from the client, including a content-length.
+ net::SpdyNameValueBlock headers;
+ headers["content-length"] = "22";
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Now send a few DATA frames.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(false, "stuff.\n");
+
+ // Finish with a HEADERS frame.
+ net::SpdyNameValueBlock headers2;
+ headers2["x-metadata"] = "foobar";
+ headers2["x-whatever"] = "quux";
+ PostHeadersFrame(true, headers2);
+
+ // Read in all the data. Because we supplied a content-length, chunked
+ // encoding should not be used, and as an unfortunate consequence, we must
+ // therefore ignore the trailing headers (justified in that, at least in
+ // HTTP, they're generally only used for ignorable metadata; in fact, they're
+ // not generally used at all).
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ // One (usually irrelevant) quirk of our implementation is that the host
+ // header appears in a slightly different place for SPDY v2 and SPDY v3.
+ // This is beacuse in SPDY v3 the host header is ":host", which sorts
+ // earlier, and which we transform into the HTTP header "host".
+ if (is_spdy2()) {
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "content-length: 22\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "Please do some stuff.\n");
+ } else {
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "content-length: 22\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "Please do some stuff.\n");
+ }
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, ExtraSynStream) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.com";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.com/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/erase/the/whole/database.cgi";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Read in all available data.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /erase/the/whole/database.cgi HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "referer: https://www.example.com/index.html\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n");
+ ExpectEndOfBrigade();
+
+ // Now send another SYN_STREAM for the same stream_id, which is illegal.
+ PostSynStreamFrame(false, headers);
+ // If we try to read more data, we'll get nothing.
+ ASSERT_TRUE(APR_STATUS_IS_ECONNABORTED(
+ Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0)));
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ // The stream should have been aborted.
+ ExpectRstStream(net::RST_STREAM_PROTOCOL_ERROR);
+ ExpectNoMoreOutputFrames();
+ EXPECT_TRUE(stream_.is_aborted());
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, SpdyToHttpFilterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
--- /dev/null
+/* 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.
+ */
+// Contains IdPool, a class for managing 16-bit process-global IDs.
+
+#include "mod_spdy/apache/id_pool.h"
+
+#include <vector>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace mod_spdy {
+
+IdPool* IdPool::g_instance = NULL;
+const uint16 IdPool::kOverFlowId;
+
+IdPool::IdPool()
+ : next_never_used_(0) /* So it gets incremented to 1 in ::Alloc */ {
+}
+
+IdPool::~IdPool() {
+}
+
+void IdPool::CreateInstance() {
+ DCHECK(g_instance == NULL);
+ g_instance = new IdPool();
+}
+
+void IdPool::DestroyInstance() {
+ DCHECK(g_instance != NULL);
+ delete g_instance;
+ g_instance = NULL;
+}
+
+uint16 IdPool::Alloc() {
+ base::AutoLock lock(mutex_);
+ if (!free_list_.empty()) {
+ uint16 id = free_list_.back();
+ free_list_.pop_back();
+ alloc_set_.insert(id);
+ return id;
+ }
+
+ // We do not use 0 or kOverFlowId normally..
+ if (alloc_set_.size() == (0x10000 - 2)) {
+ LOG(WARNING) << "Out of slave fetch IDs, things may break";
+ return kOverFlowId;
+ }
+
+ // Freelist is empty, but we haven't yet used some ID, so return it.
+ ++next_never_used_;
+ DCHECK(next_never_used_ != kOverFlowId);
+ DCHECK(alloc_set_.find(next_never_used_) == alloc_set_.end());
+ alloc_set_.insert(next_never_used_);
+ return next_never_used_;
+}
+
+void IdPool::Free(uint16 id) {
+ if (id == kOverFlowId) {
+ return;
+ }
+
+ base::AutoLock lock(mutex_);
+ DCHECK(alloc_set_.find(id) != alloc_set_.end());
+ alloc_set_.erase(id);
+ free_list_.push_back(id);
+}
+
+} // namespace mod_spdy
--- /dev/null
+/* 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.
+ */
+
+#ifndef MOD_SPDY_APACHE_ID_POOL_H_
+#define MOD_SPDY_APACHE_ID_POOL_H_
+
+#include <vector>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+
+namespace mod_spdy {
+
+// A class for managing non-zero 16-bit process-global IDs.
+class IdPool {
+ public:
+ static const uint16 kOverFlowId = 0xFFFF;
+
+ // Returns the one and only instance of the IdPool. Note that one must
+ // be created with CreateInstance().
+ static IdPool* Instance() { return g_instance; }
+
+ // Call this before threading starts to initialize the instance pointer.
+ static void CreateInstance();
+
+ // Call this once you're done with the pool object to delete it.
+ static void DestroyInstance();
+
+ // Allocates a new, distinct, non-zero ID. 2^16-2 possible values may be
+ // returned; if more than that are needed simultaneously (without being
+ // Free()d) kOverFlowId will always be returned.
+ uint16 Alloc();
+
+ // Release an ID that's no longer in use, making it available for further
+ // calls to Alloc().
+ void Free(uint16 id);
+
+ private:
+ IdPool();
+ ~IdPool();
+
+ static IdPool* g_instance;
+
+ base::Lock mutex_;
+ std::vector<uint16> free_list_; // IDs known to be free
+ std::set<uint16> alloc_set_; // IDs currently in use
+ uint16 next_never_used_; // Next ID we have never returned from Alloc,
+ // for use when the free list is empty.
+
+ DISALLOW_COPY_AND_ASSIGN(IdPool);
+};
+
+} // namespace mod_spdy
+
+#endif /* MOD_SPDY_APACHE_ID_POOL_H_ */
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/id_pool.h"
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using mod_spdy::IdPool;
+
+TEST(IdPoolTest, Lifetime) {
+ EXPECT_EQ(NULL, IdPool::Instance());
+ IdPool::CreateInstance();
+ EXPECT_TRUE(IdPool::Instance() != NULL);
+ IdPool::DestroyInstance();
+ EXPECT_EQ(NULL, IdPool::Instance());
+}
+
+TEST(IdPoolTest, BasicAllocation) {
+ IdPool::CreateInstance();
+ IdPool* instance = IdPool::Instance();
+ uint16 id_1 = instance->Alloc();
+ uint16 id_2 = instance->Alloc();
+ uint16 id_3 = instance->Alloc();
+ EXPECT_NE(0, id_1);
+ EXPECT_NE(0, id_2);
+ EXPECT_NE(0, id_3);
+ EXPECT_NE(id_1, id_2);
+ EXPECT_NE(id_1, id_3);
+ EXPECT_NE(id_2, id_3);
+ instance->Free(id_1);
+ instance->Free(id_2);
+ instance->Free(id_3);
+ IdPool::DestroyInstance();
+}
+
+TEST(IdPoolTest, AllocatingMany) {
+ // We should be able to allocate 2^16-2 unique ids.
+ IdPool::CreateInstance();
+ IdPool* instance = IdPool::Instance();
+
+ std::set<uint16> in_use;
+ for (int run = 0; run < 0xFFFE; ++run) {
+ uint16 new_id = instance->Alloc();
+ EXPECT_NE(0, new_id);
+ EXPECT_NE(IdPool::kOverFlowId, new_id);
+ EXPECT_TRUE(in_use.find(new_id) == in_use.end());
+ in_use.insert(new_id);
+ }
+
+ // All attempts after this point should return kOverFlowId.
+ for (int run = 0; run < 100; ++run) {
+ EXPECT_EQ(IdPool::kOverFlowId, instance->Alloc());
+ }
+
+ // Trying to free the overflow ID is harmless.
+ instance->Free(IdPool::kOverFlowId);
+
+ // Now delete half of them.
+ int deleted = 0;
+ std::set<uint16>::iterator i = in_use.begin();
+ while (deleted != 0xFFFE / 2) {
+ ASSERT_TRUE(i != in_use.end());
+ instance->Free(*i);
+ ++deleted;
+ in_use.erase(i);
+ i = in_use.begin();
+ }
+
+ // Should now be able to allocate that many again.
+ for (int run = 0; run < 0xFFFE / 2; ++run) {
+ uint16 new_id = instance->Alloc();
+ EXPECT_NE(0, new_id);
+ EXPECT_NE(IdPool::kOverFlowId, new_id);
+ EXPECT_TRUE(in_use.find(new_id) == in_use.end());
+ in_use.insert(new_id);
+ }
+
+ IdPool::DestroyInstance();
+}
+
+} // namespace
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/apache/log_message_handler.h"
+
+#include <limits>
+#include <string>
+
+#include "httpd.h"
+// When HAVE_SYSLOG is defined, apache http_log.h will include syslog.h, which
+// #defined LOG_* as numbers. This conflicts with what we are using those here.
+#undef HAVE_SYSLOG
+#include "http_log.h"
+
+#include "base/debug/debugger.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/threading/thread_local.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/version.h"
+
+// Make sure we don't attempt to use LOG macros here, since doing so
+// would cause us to go into an infinite log loop.
+#undef LOG
+#define LOG USING_LOG_HERE_WOULD_CAUSE_INFINITE_RECURSION
+
+namespace {
+
+class LogHandler;
+
+const char* const kLogMessagePrefix =
+ "[mod_spdy/" MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING "] ";
+
+apr_pool_t* log_pool = NULL;
+base::ThreadLocalPointer<LogHandler>* gThreadLocalLogHandler = NULL;
+
+const int kMaxInt = std::numeric_limits<int>::max();
+int log_level_cutoff = kMaxInt;
+
+class LogHandler {
+ public:
+ explicit LogHandler(LogHandler* parent) : parent_(parent) {}
+ virtual ~LogHandler() {}
+ virtual void Log(int log_level, const std::string& message) = 0;
+ LogHandler* parent() const { return parent_; }
+ private:
+ LogHandler* parent_;
+ DISALLOW_COPY_AND_ASSIGN(LogHandler);
+};
+
+// Log a message with the given LogHandler; if the LogHandler is NULL, fall
+// back to using ap_log_perror.
+void LogWithHandler(LogHandler* handler, int log_level,
+ const std::string& message) {
+ if (handler != NULL) {
+ handler->Log(log_level, message);
+ } else {
+ // ap_log_perror only prints messages with a severity of at least NOTICE,
+ // so if we're falling back to ap_log_perror (which should be rare) then
+ // force the log_level to a verbosity of NOTICE or lower.
+ COMPILE_ASSERT(APLOG_DEBUG > APLOG_NOTICE,
+ higher_verbosity_is_higher_number);
+ ap_log_perror(APLOG_MARK, std::min(log_level, APLOG_NOTICE), APR_SUCCESS,
+ log_pool, "%s", message.c_str());
+ }
+}
+
+void PopLogHandler() {
+ CHECK(gThreadLocalLogHandler);
+ LogHandler* handler = gThreadLocalLogHandler->Get();
+ CHECK(handler);
+ gThreadLocalLogHandler->Set(handler->parent());
+ delete handler;
+}
+
+class ServerLogHandler : public LogHandler {
+ public:
+ ServerLogHandler(LogHandler* parent, server_rec* server)
+ : LogHandler(parent), server_(server) {}
+ virtual void Log(int log_level, const std::string& message) {
+ ap_log_error(APLOG_MARK, log_level, APR_SUCCESS, server_,
+ "%s", message.c_str());
+ }
+ private:
+ server_rec* const server_;
+ DISALLOW_COPY_AND_ASSIGN(ServerLogHandler);
+};
+
+class ConnectionLogHandler : public LogHandler {
+ public:
+ ConnectionLogHandler(LogHandler* parent, conn_rec* connection)
+ : LogHandler(parent), connection_(connection) {}
+ virtual void Log(int log_level, const std::string& message) {
+ ap_log_cerror(APLOG_MARK, log_level, APR_SUCCESS, connection_,
+ "%s", message.c_str());
+ }
+ private:
+ conn_rec* const connection_;
+ DISALLOW_COPY_AND_ASSIGN(ConnectionLogHandler);
+};
+
+class StreamLogHandler : public LogHandler {
+ public:
+ StreamLogHandler(LogHandler* parent, conn_rec* connection,
+ const mod_spdy::SpdyStream* stream)
+ : LogHandler(parent), connection_(connection), stream_(stream) {}
+ virtual void Log(int log_level, const std::string& message) {
+ ap_log_cerror(APLOG_MARK, log_level, APR_SUCCESS, connection_,
+ "[stream %d] %s", static_cast<int>(stream_->stream_id()),
+ message.c_str());
+ }
+ private:
+ conn_rec* const connection_;
+ const mod_spdy::SpdyStream* const stream_;
+ DISALLOW_COPY_AND_ASSIGN(StreamLogHandler);
+};
+
+int GetApacheLogLevel(int severity) {
+ switch (severity) {
+ case logging::LOG_INFO:
+ return APLOG_INFO;
+ case logging::LOG_WARNING:
+ return APLOG_WARNING;
+ case logging::LOG_ERROR:
+ return APLOG_ERR;
+ case logging::LOG_ERROR_REPORT:
+ return APLOG_CRIT;
+ case logging::LOG_FATAL:
+ return APLOG_ALERT;
+ default: // For VLOG()s
+ return APLOG_DEBUG;
+ }
+}
+
+bool LogMessageHandler(int severity, const char* file, int line,
+ size_t message_start, const std::string& str) {
+ const int this_log_level = GetApacheLogLevel(severity);
+
+ std::string message(kLogMessagePrefix);
+ message.append(str);
+ if (severity == logging::LOG_FATAL) {
+ if (base::debug::BeingDebugged()) {
+ base::debug::BreakDebugger();
+ } else {
+ base::debug::StackTrace trace;
+ std::ostringstream stream;
+ trace.OutputToStream(&stream);
+ message.append(stream.str());
+ }
+ }
+
+ // Trim the newline off the end of the message string.
+ size_t last_msg_character_index = message.length() - 1;
+ if (message[last_msg_character_index] == '\n') {
+ message.resize(last_msg_character_index);
+ }
+
+ if (this_log_level <= log_level_cutoff || log_level_cutoff == kMaxInt) {
+ LogWithHandler(gThreadLocalLogHandler->Get(), this_log_level, message);
+ }
+
+ if (severity == logging::LOG_FATAL) {
+ // Crash the process to generate a dump.
+ base::debug::BreakDebugger();
+ }
+
+ return true;
+}
+
+// Include PID and TID in each log message.
+bool kShowProcessId = true;
+bool kShowThreadId = true;
+
+// Disabled since this information is already included in the apache
+// log line.
+bool kShowTimestamp = false;
+
+// Disabled by default due to CPU cost. Enable to see high-resolution
+// timestamps in the logs.
+bool kShowTickcount = false;
+
+} // namespace
+
+namespace mod_spdy {
+
+ScopedServerLogHandler::ScopedServerLogHandler(server_rec* server) {
+ CHECK(gThreadLocalLogHandler);
+ gThreadLocalLogHandler->Set(new ServerLogHandler(
+ gThreadLocalLogHandler->Get(), server));
+}
+
+ScopedServerLogHandler::~ScopedServerLogHandler() {
+ PopLogHandler();
+}
+
+ScopedConnectionLogHandler::ScopedConnectionLogHandler(conn_rec* connection) {
+ CHECK(gThreadLocalLogHandler);
+ gThreadLocalLogHandler->Set(new ConnectionLogHandler(
+ gThreadLocalLogHandler->Get(), connection));
+}
+
+ScopedConnectionLogHandler::~ScopedConnectionLogHandler() {
+ PopLogHandler();
+}
+
+ScopedStreamLogHandler::ScopedStreamLogHandler(conn_rec* slave_connection,
+ const SpdyStream* stream) {
+ CHECK(gThreadLocalLogHandler);
+ gThreadLocalLogHandler->Set(new StreamLogHandler(
+ gThreadLocalLogHandler->Get(), slave_connection, stream));
+}
+
+ScopedStreamLogHandler::~ScopedStreamLogHandler() {
+ PopLogHandler();
+}
+
+void InstallLogMessageHandler(apr_pool_t* pool) {
+ log_pool = pool;
+ gThreadLocalLogHandler = new base::ThreadLocalPointer<LogHandler>();
+ PoolRegisterDelete(pool, gThreadLocalLogHandler);
+ logging::SetLogItems(kShowProcessId,
+ kShowThreadId,
+ kShowTimestamp,
+ kShowTickcount);
+ logging::SetLogMessageHandler(&LogMessageHandler);
+}
+
+void SetLoggingLevel(int apache_log_level, int vlog_level) {
+ switch (apache_log_level) {
+ case APLOG_EMERG:
+ case APLOG_ALERT:
+ logging::SetMinLogLevel(logging::LOG_FATAL);
+ break;
+ case APLOG_CRIT:
+ logging::SetMinLogLevel(logging::LOG_ERROR_REPORT);
+ break;
+ case APLOG_ERR:
+ logging::SetMinLogLevel(logging::LOG_ERROR);
+ break;
+ case APLOG_WARNING:
+ logging::SetMinLogLevel(logging::LOG_WARNING);
+ break;
+ case APLOG_NOTICE:
+ case APLOG_INFO:
+ case APLOG_DEBUG:
+ default:
+ logging::SetMinLogLevel(std::min(logging::LOG_INFO, -vlog_level));
+ break;
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_APACHE_LOG_MESSAGE_HANDLER_H_
+#define MOD_SPDY_APACHE_LOG_MESSAGE_HANDLER_H_
+
+#include <string>
+
+#include "httpd.h"
+#include "apr_pools.h"
+
+#include "base/basictypes.h"
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// Stack-allocate to install a server-specific log handler for the duration of
+// the current scope (on the current thread only). For example:
+//
+// void SomeApacheHookFunction(server_rec* server) {
+// ScopedServerLogHandler handler(server);
+// ...call various other functions here...
+// }
+//
+// The log handler will be in effect until the end of the block, but only for
+// this thread (even if this thread spawns other threads in the meantime).
+// Establishing this server-specific log handler allows LOG() macros within
+// called functions to produce better messages.
+class ScopedServerLogHandler {
+ public:
+ explicit ScopedServerLogHandler(server_rec* server);
+ ~ScopedServerLogHandler();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedServerLogHandler);
+};
+
+// Stack-allocate to install a connection-specific log handler for the duration
+// of the current scope (on the current thread only). See the doc comment for
+// ScopedServerLogHandler above for an example.
+class ScopedConnectionLogHandler {
+ public:
+ explicit ScopedConnectionLogHandler(conn_rec* connection);
+ ~ScopedConnectionLogHandler();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedConnectionLogHandler);
+};
+
+// Stack-allocate to install a stream-specific log handler for the duration of
+// the current scope (on the current thread only). See the doc comment for
+// ScopedServerLogHandler above for an example.
+class ScopedStreamLogHandler {
+ public:
+ explicit ScopedStreamLogHandler(conn_rec* slave_connection,
+ const SpdyStream* stream);
+ ~ScopedStreamLogHandler();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedStreamLogHandler);
+};
+
+// Install a log message handler that routes LOG() messages to the
+// apache error log. Should be called once, at server startup.
+void InstallLogMessageHandler(apr_pool_t* pool);
+
+// Set the logging level for LOG() messages, based on the Apache log level and
+// the VLOG-level specified in the server config. Note that the VLOG level
+// will be ignored unless the Apache log verbosity is at NOTICE or higher.
+// Should be called once for each child process, at process startup.
+void SetLoggingLevel(int apache_log_level, int vlog_level);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_LOG_MESSAGE_HANDLER_H_
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/apache/master_connection_context.h"
+
+#include "base/logging.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_stream.h"
+
+namespace mod_spdy {
+
+MasterConnectionContext::MasterConnectionContext(bool using_ssl)
+ : using_ssl_(using_ssl),
+ npn_state_(NOT_DONE_YET),
+ assume_spdy_(false),
+ spdy_version_(spdy::SPDY_VERSION_NONE) {}
+
+MasterConnectionContext::~MasterConnectionContext() {}
+
+bool MasterConnectionContext::is_using_spdy() const {
+ const bool using_spdy = (npn_state_ == USING_SPDY || assume_spdy_);
+ return using_spdy;
+}
+
+MasterConnectionContext::NpnState MasterConnectionContext::npn_state() const {
+ return npn_state_;
+}
+
+void MasterConnectionContext::set_npn_state(NpnState state) {
+ npn_state_ = state;
+}
+
+bool MasterConnectionContext::is_assuming_spdy() const {
+ return assume_spdy_;
+}
+
+void MasterConnectionContext::set_assume_spdy(bool assume) {
+ assume_spdy_ = assume;
+}
+
+spdy::SpdyVersion MasterConnectionContext::spdy_version() const {
+ DCHECK(is_using_spdy());
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version_);
+ return spdy_version_;
+}
+
+void MasterConnectionContext::set_spdy_version(
+ spdy::SpdyVersion spdy_version) {
+ DCHECK(is_using_spdy());
+ DCHECK_EQ(spdy::SPDY_VERSION_NONE, spdy_version_);
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ spdy_version_ = spdy_version;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_APACHE_MASTER_CONNECTION_CONTEXT_H_
+#define MOD_SPDY_APACHE_MASTER_CONNECTION_CONTEXT_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// Shared context object for a SPDY connection to the outside world.
+class MasterConnectionContext {
+ public:
+ // Create a context object for a master connection (one to the outside world,
+ // not for talking to Apache).
+ explicit MasterConnectionContext(bool using_ssl);
+ ~MasterConnectionContext();
+
+ // Return true if the connection to the user is over SSL. This is almost
+ // always true, but may be false if we've been set to use SPDY for non-SSL
+ // connections (for debugging).
+ bool is_using_ssl() const { return using_ssl_; }
+
+ // Return true if we are using SPDY for this connection, which is the case if
+ // either 1) SPDY was chosen by NPN, or 2) we are assuming SPDY regardless of
+ // NPN.
+ bool is_using_spdy() const;
+
+ enum NpnState {
+ // NOT_DONE_YET: NPN has not yet completed.
+ NOT_DONE_YET,
+ // USING_SPDY: We have agreed with the client to use SPDY for this
+ // connection.
+ USING_SPDY,
+ // NOT_USING_SPDY: We have decided not to use SPDY for this connection.
+ NOT_USING_SPDY
+ };
+
+ // Get the NPN state of this connection. Unless you actually care about NPN
+ // itself, you probably don't want to use this method to check if SPDY is
+ // being used; instead, use is_using_spdy().
+ NpnState npn_state() const;
+
+ // Set the NPN state of this connection.
+ void set_npn_state(NpnState state);
+
+ // If true, we are simply _assuming_ SPDY, regardless of the outcome of NPN.
+ bool is_assuming_spdy() const;
+
+ // Set whether we are assuming SPDY for this connection (regardless of NPN).
+ void set_assume_spdy(bool assume);
+
+ // Return the SPDY version number we will be using. Requires that
+ // is_using_spdy() is true and that the version number has already been set.
+ spdy::SpdyVersion spdy_version() const;
+
+ // Set the SPDY version number we will be using. Requires that
+ // is_using_spdy() is true, and set_spdy_version hasn't already been called.
+ void set_spdy_version(spdy::SpdyVersion spdy_version);
+
+ private:
+ const bool using_ssl_;
+ NpnState npn_state_;
+ bool assume_spdy_;
+ spdy::SpdyVersion spdy_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(MasterConnectionContext);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_MASTER_CONNECTION_CONTEXT_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/pool_util.h"
+
+#include <string>
+
+#include "apr_errno.h"
+
+#include "base/basictypes.h"
+
+namespace mod_spdy {
+
+std::string AprStatusString(apr_status_t status) {
+ char buffer[120];
+ apr_strerror(status, buffer, arraysize(buffer));
+ return std::string(buffer);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_APACHE_POOL_UTIL_H_
+#define MOD_SPDY_APACHE_POOL_UTIL_H_
+
+#include <string>
+
+#include "apr_pools.h"
+#include "base/logging.h"
+
+namespace mod_spdy {
+
+/**
+ * Wrapper object that creates a new apr_pool_t and then destroys it when
+ * deleted (handy for creating a local apr_pool_t on the stack).
+ *
+ * Example usage:
+ *
+ * apr_status_t SomeFunction() {
+ * LocalPool local;
+ * char* buffer = apr_palloc(local.pool(), 1024);
+ * // Do stuff with buffer; it will dealloc when we leave this scope.
+ * return APR_SUCCESS;
+ * }
+ */
+class LocalPool {
+ public:
+ LocalPool() : pool_(NULL) {
+ // apr_pool_create() only fails if we run out of memory. However, we make
+ // no effort elsewhere in this codebase to deal with running out of memory,
+ // so there's no sense in dealing with it here. Instead, just assert that
+ // pool creation succeeds.
+ const apr_status_t status = apr_pool_create(&pool_, NULL);
+ CHECK(status == APR_SUCCESS);
+ CHECK(pool_ != NULL);
+ }
+
+ ~LocalPool() {
+ apr_pool_destroy(pool_);
+ }
+
+ apr_pool_t* pool() const { return pool_; }
+
+ private:
+ apr_pool_t* pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalPool);
+};
+
+// Helper function for PoolRegisterDelete.
+template <class T>
+apr_status_t DeletionFunction(void* object) {
+ delete static_cast<T*>(object);
+ return APR_SUCCESS;
+}
+
+// Register a C++ object to be deleted with a pool.
+template <class T>
+void PoolRegisterDelete(apr_pool_t* pool, T* object) {
+ // Note that the "child cleanup" argument below doesn't apply to us, so we
+ // use apr_pool_cleanup_null, which is a no-op cleanup function.
+ apr_pool_cleanup_register(pool, object,
+ DeletionFunction<T>, // cleanup function
+ apr_pool_cleanup_null); // child cleanup
+}
+
+// Un-register a C++ object from deletion with a pool. Essentially, this
+// undoes a previous call to PoolRegisterDelete with the same pool and object.
+template <class T>
+void PoolUnregisterDelete(apr_pool_t* pool, T* object) {
+ apr_pool_cleanup_kill(pool, object, DeletionFunction<T>);
+}
+
+// Return a string describing the given APR status code.
+std::string AprStatusString(apr_status_t status);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_POOL_UTIL_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/pool_util.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Class to help us detect when it is deleted.
+class SetOnDelete {
+ public:
+ SetOnDelete(int value, int* ptr) : value_(value), ptr_(ptr) {}
+ ~SetOnDelete() { *ptr_ = value_; }
+ private:
+ const int value_;
+ int* const ptr_;
+ DISALLOW_COPY_AND_ASSIGN(SetOnDelete);
+};
+
+TEST(PoolUtilTest, LocalPoolRegisterDelete) {
+ int value = 3;
+ {
+ mod_spdy::LocalPool local;
+ SetOnDelete* setter = new SetOnDelete(5, &value);
+ mod_spdy::PoolRegisterDelete(local.pool(), setter);
+ ASSERT_EQ(3, value);
+ }
+ ASSERT_EQ(5, value);
+}
+
+TEST(PoolUtilTest, LocalPoolUnregisterDelete) {
+ int value = 2;
+ SetOnDelete* setter = new SetOnDelete(7, &value);
+ {
+ mod_spdy::LocalPool local;
+ mod_spdy::PoolRegisterDelete(local.pool(), setter);
+ ASSERT_EQ(2, value);
+ mod_spdy::PoolUnregisterDelete(local.pool(), setter);
+ ASSERT_EQ(2, value);
+ }
+ ASSERT_EQ(2, value);
+ delete setter;
+ ASSERT_EQ(7, value);
+}
+
+} // namespace
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/apache/slave_connection.h"
+
+#include "apr_strings.h"
+// Temporarily define CORE_PRIVATE so we can see the declarations for
+// ap_create_conn_config (in http_config.h), ap_process_connection (in
+// http_connection.h), and core_module (in http_core.h).
+#define CORE_PRIVATE
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_core.h"
+#undef CORE_PRIVATE
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mod_spdy/apache/config_util.h"
+#include "mod_spdy/apache/id_pool.h"
+#include "mod_spdy/apache/log_message_handler.h"
+#include "mod_spdy/apache/master_connection_context.h"
+#include "mod_spdy/apache/slave_connection_context.h"
+#include "mod_spdy/apache/sockaddr_util.h"
+#include "mod_spdy/apache/ssl_util.h"
+
+namespace mod_spdy {
+
+SlaveConnectionFactory::SlaveConnectionFactory(conn_rec* master_connection) {
+ // If the parent connection is using mod_spdy, we can extract relevant info
+ // on whether we're using it there.
+ if (HasMasterConnectionContext(master_connection)) {
+ MasterConnectionContext* master_context =
+ GetMasterConnectionContext(master_connection);
+ is_using_ssl_ = master_context->is_using_ssl();
+ spdy_version_ = (master_context->is_using_spdy() ?
+ master_context->spdy_version() :
+ spdy::SPDY_VERSION_NONE);
+ } else {
+ is_using_ssl_ = IsUsingSslForConnection(master_connection);
+ spdy_version_ = spdy::SPDY_VERSION_NONE;
+ }
+
+ base_server_ = master_connection->base_server;
+ local_addr_ = DeepCopySockAddr(master_connection->local_addr, pool_.pool());
+ local_ip_ = apr_pstrdup(pool_.pool(), master_connection->local_ip);
+ remote_addr_ = DeepCopySockAddr(master_connection->remote_addr, pool_.pool());
+ remote_ip_ = apr_pstrdup(pool_.pool(), master_connection->remote_ip);
+ master_connection_id_ = master_connection->id;
+}
+
+SlaveConnectionFactory::~SlaveConnectionFactory() {
+ // Nothing to do --- pool_ dtor will clean everything up.
+}
+
+SlaveConnection* SlaveConnectionFactory::Create() {
+ return new SlaveConnection(this);
+}
+
+SlaveConnection::SlaveConnection(SlaveConnectionFactory* factory) {
+ apr_pool_t* pool = pool_.pool();
+
+ slave_connection_ =
+ static_cast<conn_rec*>(apr_pcalloc(pool, sizeof(conn_rec)));
+
+ // Initialize what fields of the connection object we can (the rest are
+ // zeroed out by apr_pcalloc). In particular, we should set at least those
+ // fields set by core_create_conn() in core.c in Apache.
+ // -> id will be set once we are actually running the connection, in
+ // ::Run().
+ slave_connection_->clogging_input_filters = 0;
+ slave_connection_->sbh = NULL;
+ // We will manage this connection and all the associated resources with the
+ // pool we just created.
+ slave_connection_->pool = pool;
+ slave_connection_->bucket_alloc = apr_bucket_alloc_create(pool);
+ slave_connection_->conn_config = ap_create_conn_config(pool);
+ slave_connection_->notes = apr_table_make(pool, 5);
+ // Use the same server settings and client address for the slave connection
+ // as for the master connection --- the factory saved them for us.
+ slave_connection_->base_server = factory->base_server_;
+ slave_connection_->local_addr = factory->local_addr_;
+ slave_connection_->local_ip = factory->local_ip_;
+ slave_connection_->remote_addr = factory->remote_addr_;
+ slave_connection_->remote_ip = factory->remote_ip_;
+
+ // One of the other things we will need in slave_connection is a
+ // connection id. One of the bits of info we will need for it is the
+ // id of the master connection. We save it here, and use it inside ::Run().
+ master_connection_id_ = factory->master_connection_id_;
+
+ // We're supposed to pass a socket object to ap_process_connection below, but
+ // there's no meaningful object to pass for this slave connection, because
+ // we're not really talking to the network. Our pre-connection hook will
+ // prevent the core filters, which talk to the socket, from being inserted,
+ // so they won't notice anyway; nonetheless, we can't pass NULL to
+ // ap_process_connection because that can cause some other modules to
+ // segfault if they try to muck with the socket's settings. So, we'll just
+ // allocate our own socket object for those modules to mess with. This is a
+ // kludge, but it seems to work.
+ slave_socket_ = NULL;
+ apr_status_t status = apr_socket_create(
+ &slave_socket_, APR_INET, SOCK_STREAM, APR_PROTO_TCP, pool);
+ DCHECK(status == APR_SUCCESS);
+ DCHECK(slave_socket_ != NULL);
+
+ // In our context object for this connection, mark this connection as being
+ // a slave. Our pre-connection and process-connection hooks will notice
+ // this, and act accordingly, when they are called for the slave
+ // connection.
+ SlaveConnectionContext* slave_context =
+ CreateSlaveConnectionContext(slave_connection_);
+
+ // Now store the SSL and SPDY info.
+ slave_context->set_is_using_ssl(factory->is_using_ssl_);
+ slave_context->set_spdy_version(factory->spdy_version_);
+}
+
+SlaveConnection::~SlaveConnection() {
+ // pool_ destructor will take care of everything.
+}
+
+SlaveConnectionContext* SlaveConnection::GetSlaveConnectionContext() {
+ return mod_spdy::GetSlaveConnectionContext(slave_connection_);
+}
+
+void SlaveConnection::Run() {
+ // Pick a globally-unique ID for the slave connection; this must be unique
+ // at any given time. Normally the MPM is responsible for assigning these,
+ // and each MPM does it differently, so we're cheating in a dangerous way by
+ // trying to assign one here. However, most MPMs seem to do it in a similar
+ // way: for non-threaded MPMs (e.g. Prefork, WinNT), the ID is just the
+ // child ID, which is a small nonnegative integer (i.e. an array index into
+ // the list of active child processes); for threaded MPMs (e.g. Worker,
+ // Event) the ID is typically ((child_index * thread_limit) + thread_index),
+ // which will again be a positive integer, most likely (but not necessarily,
+ // if thread_limit is set absurdly high) smallish.
+ //
+ // Therefore, the approach that we take is to concatenate the Apache
+ // connection ID for the master connection with a small integer from IDPool
+ // that's unique within the process, and, to avoid conflicts with
+ // MPM-assigned connection IDs, we make our slave connection ID negative.
+ // We only have so many bits to work with
+ // (especially if long is only four bytes instead of eight), so we could
+ // potentially run into trouble if the master connection ID gets very large
+ // or we have too many active tasks simultaneously (i.e. more than 2^16).
+ // So, this approach definitely isn't any kind of robust; but it will
+ // probably usually work. It would, of course, be great to replace this
+ // with a better strategy, if we find one.
+ //
+ // TODO(mdsteele): We could also consider using an #if here to widen the
+ // masks and the shift distance on systems where sizeof(long)==8.
+ // We might as well use those extra bits if we have them.
+ COMPILE_ASSERT(sizeof(long) >= 4, long_is_at_least_32_bits);
+ const uint16 in_process_id = IdPool::Instance()->Alloc();
+ const long slave_connectionid =
+ -(((master_connection_id_ & 0x7fffL) << 16) | in_process_id);
+ slave_connection_->id = slave_connectionid;
+
+ // Normally, the core pre-connection hook sets the core module's connection
+ // context to the socket passed to ap_process_connection; certain other
+ // modules, such as mod_reqtimeout, read the core module's connection
+ // context directly so as to read this socket's settings. However, we
+ // purposely don't allow the core pre-connection hook to run, because we
+ // don't want the core connection filters to be inserted. So, to avoid
+ // breaking other modules, we take it upon oursevles to set the core
+ // module's connection context to the socket we are passing to
+ // ap_process_connection. This is ugly, but seems to work.
+ ap_set_module_config(slave_connection_->conn_config,
+ &core_module, slave_socket_);
+
+ // Invoke Apache's usual processing pipeline. This will block until the
+ // connection is complete.
+ ap_process_connection(slave_connection_, slave_socket_);
+
+ IdPool::Instance()->Free(in_process_id);
+}
+
+} // namespace mod_spdy
--- /dev/null
+/* 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.
+ */
+
+#ifndef MOD_SPDY_APACHE_SLAVE_CONNECTION_H_
+#define MOD_SPDY_APACHE_SLAVE_CONNECTION_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/protocol_util.h"
+
+struct apr_sockaddr_t;
+struct apr_socket_t;
+struct conn_rec;
+struct server_rec;
+
+namespace mod_spdy {
+
+class SlaveConnection;
+class SlaveConnectionContext;
+
+// SlaveConnectionFactory + SlaveConnection helps execute requests within
+// the current Apache process, with the request and response both going to
+// some other code and not an external client talking over TCP.
+//
+// SlaveConnectionFactory + SlaveConnection help create a fake Apache conn_rec
+// object and run it. That conn_rec will have a SlaveConnectionContext
+// attached to it, which various hooks in mod_spdy.cc will recognize and handle
+// specially. In particular, they will arrange to have the I/O for connection
+// routed to and from the input & output filters set on the
+// SlaveConnectionContext.
+class SlaveConnectionFactory {
+ public:
+ // Prepares the factory to create slave connections with endpoint, SPDY and
+ // SSL information matching that of the master_connection.
+ //
+ // Does not retain any pointers to data from master_connection, so may be
+ // used after master_connection is destroyed.
+ explicit SlaveConnectionFactory(conn_rec* master_connection);
+
+ ~SlaveConnectionFactory();
+
+ // Creates a slave connection matching the settings in the constructor.
+ // You should attach I/O filters on its GetSlaveConnectionContext() before
+ // calling Run().
+ //
+ // The resulted object lives on the C++ heap, and must be deleted.
+ SlaveConnection* Create();
+
+ private:
+ friend class SlaveConnection;
+
+ // Saved information from master_connection
+ bool is_using_ssl_;
+ spdy::SpdyVersion spdy_version_;
+ server_rec* base_server_;
+ LocalPool pool_;
+ // All of these are in pool_:
+ apr_sockaddr_t* local_addr_;
+ char* local_ip_;
+ apr_sockaddr_t* remote_addr_;
+ char* remote_ip_;
+ long master_connection_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(SlaveConnectionFactory);
+};
+
+class SlaveConnection {
+ public:
+ ~SlaveConnection();
+
+ // Returns the Apache conn_rec object this manages.
+ conn_rec* apache_connection() { return slave_connection_; }
+
+ // Returns the underlying SlaveConnectionContext, which lets you query
+ // information about the connection and hook in I/O filters.
+ //
+ // This is the same as GetSlaveConnectionContext(apache_connection()), and
+ // can thus be accessed via the conn_rec* as well.
+ SlaveConnectionContext* GetSlaveConnectionContext();
+
+ // Executes the requests associated with this connection, taking a request
+ // from the input filter set on the SlaveConnectionContext(), and directing
+ // the response to the output filter. Note that this is a blocking operation.
+ void Run();
+
+ private:
+ SlaveConnection(SlaveConnectionFactory* factory);
+ friend class SlaveConnectionFactory;
+
+ LocalPool pool_;
+ conn_rec* slave_connection_; // owned by pool_
+ apr_socket_t* slave_socket_; // owned by pool_
+ long master_connection_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(SlaveConnection);
+};
+
+} // namespace mod_spdy
+
+#endif /* MOD_SPDY_APACHE_SLAVE_CONNECTION_H_ */
--- /dev/null
+/* 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.
+ */
+
+#include "mod_spdy/apache/slave_connection_api.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/apache/slave_connection.h"
+#include "mod_spdy/apache/slave_connection_context.h"
+
+using mod_spdy::SlaveConnection;
+using mod_spdy::SlaveConnectionContext;
+using mod_spdy::SlaveConnectionFactory;
+
+struct spdy_slave_connection_factory {
+ explicit spdy_slave_connection_factory(SlaveConnectionFactory* impl)
+ : impl(impl) {}
+ scoped_ptr<SlaveConnectionFactory> impl;
+};
+
+struct spdy_slave_connection {
+ explicit spdy_slave_connection(SlaveConnection* impl)
+ : impl(impl) {}
+ scoped_ptr<SlaveConnection> impl;
+};
+
+spdy_slave_connection_factory* spdy_create_slave_connection_factory(
+ conn_rec* master_connection) {
+ return new spdy_slave_connection_factory(
+ new SlaveConnectionFactory(master_connection));
+}
+
+void spdy_destroy_slave_connection_factory(
+ spdy_slave_connection_factory* factory) {
+ delete factory;
+}
+
+spdy_slave_connection* spdy_create_slave_connection(
+ spdy_slave_connection_factory* factory,
+ ap_filter_rec_t* input_filter,
+ void* input_filter_ctx,
+ ap_filter_rec_t* output_filter,
+ void* output_filter_ctx) {
+ spdy_slave_connection* wrapper =
+ new spdy_slave_connection(factory->impl->Create());
+
+ SlaveConnectionContext* ctx = wrapper->impl->GetSlaveConnectionContext();
+ ctx->SetInputFilter(input_filter, input_filter_ctx);
+ ctx->SetOutputFilter(output_filter, output_filter_ctx);
+
+ return wrapper;
+}
+
+void spdy_run_slave_connection(spdy_slave_connection* conn) {
+ conn->impl->Run();
+}
+
+void spdy_destroy_slave_connection(spdy_slave_connection* conn) {
+ delete conn;
+}
+
+void ModSpdyExportSlaveConnectionFunctions() {
+ APR_REGISTER_OPTIONAL_FN(spdy_create_slave_connection_factory);
+ APR_REGISTER_OPTIONAL_FN(spdy_destroy_slave_connection_factory);
+ APR_REGISTER_OPTIONAL_FN(spdy_create_slave_connection);
+ APR_REGISTER_OPTIONAL_FN(spdy_run_slave_connection);
+ APR_REGISTER_OPTIONAL_FN(spdy_destroy_slave_connection);
+}
--- /dev/null
+/* 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.
+ */
+
+/* This is a public header file, to be used by other Apache modules. So,
+ * identifiers declared here should follow Apache module naming conventions
+ * (specifically, identifiers should be lowercase_with_underscores, and our
+ * identifiers should start with the spdy_ prefix), and this header file must
+ * be valid in old-school C (not just C++). */
+
+#ifndef MOD_SPDY_APACHE_SLAVE_CONNECTION_API_H_
+#define MOD_SPDY_APACHE_SLAVE_CONNECTION_API_H_
+
+#include "httpd.h"
+#include "apr_optional.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ap_filter_rec_t;
+
+struct spdy_slave_connection_factory;
+struct spdy_slave_connection;
+
+/** Creates a factory object that can be used to make in-process pseudo-fetches
+ * with the same origin and target hosts as in master_connection
+ */
+APR_DECLARE_OPTIONAL_FN(
+ struct spdy_slave_connection_factory*,
+ spdy_create_slave_connection_factory, (conn_rec* master_connection));
+
+/** Destroys a factory object. */
+APR_DECLARE_OPTIONAL_FN(
+ void, spdy_destroy_slave_connection_factory,
+ (struct spdy_slave_connection_factory* factory));
+
+/** Asks mod_spdy to help with fetching a request on a slave connection.
+ * The input_filter must produce the request, and output_filter must
+ * handle the response. May return NULL if functionality is not available.
+ * The request will not be run until spdy_run_slave_connection() is invoked.
+ */
+APR_DECLARE_OPTIONAL_FN(
+ struct spdy_slave_connection*,
+ spdy_create_slave_connection, (
+ struct spdy_slave_connection_factory* factory,
+ struct ap_filter_rec_t* input_filter,
+ void* input_filter_ctx,
+ struct ap_filter_rec_t* output_filter,
+ void* output_filter_ctx));
+
+/** Actually performs the fetch on the object. Blocks, perhaps for a significant
+ * amount of time. */
+APR_DECLARE_OPTIONAL_FN(
+ void, spdy_run_slave_connection, (struct spdy_slave_connection* conn));
+
+/** Cleans up the connection object. Must not be in active use. */
+APR_DECLARE_OPTIONAL_FN(
+ void, spdy_destroy_slave_connection, (struct spdy_slave_connection*));
+
+/* Used by mod_spdy to setup the exports. Not exported itself */
+void ModSpdyExportSlaveConnectionFunctions(void);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MOD_SPDY_APACHE_SLAVE_CONNECTION_API_H_ */
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/slave_connection_context.h"
+
+#include "base/logging.h"
+#include "mod_spdy/common/spdy_stream.h"
+
+namespace mod_spdy {
+
+SlaveConnectionContext::SlaveConnectionContext()
+ : using_ssl_(false),
+ spdy_version_(spdy::SPDY_VERSION_NONE),
+ slave_stream_(NULL),
+ output_filter_handle_(NULL),
+ output_filter_context_(NULL),
+ input_filter_handle_(NULL),
+ input_filter_context_(NULL) {
+}
+
+SlaveConnectionContext::~SlaveConnectionContext() {}
+
+void SlaveConnectionContext::SetOutputFilter(
+ ap_filter_rec_t* handle, void* context) {
+ output_filter_handle_ = handle;
+ output_filter_context_ = context;
+}
+
+void SlaveConnectionContext::SetInputFilter(
+ ap_filter_rec_t* handle, void* context) {
+ input_filter_handle_ = handle;
+ input_filter_context_ = context;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// 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.
+
+#ifndef MOD_SPDY_APACHE_SLAVE_CONNECTION_CONTEXT_H_
+#define MOD_SPDY_APACHE_SLAVE_CONNECTION_CONTEXT_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/protocol_util.h"
+
+struct ap_filter_rec_t;
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// Context for a 'slave' connection in mod_spdy, used to represent a fetch
+// of a given URL from within Apache (as opposed to outgoing SPDY session to
+// the client, which has a ConnectionContext).
+class SlaveConnectionContext {
+ public:
+ SlaveConnectionContext();
+ ~SlaveConnectionContext();
+
+ // Return true if the connection to the user is over SSL. This is almost
+ // always true, but may be false if we've been set to use SPDY for non-SSL
+ // connections (for debugging). Note that for a slave connection, this
+ // refers to whether the master network connection is using SSL.
+ bool is_using_ssl() const { return using_ssl_; }
+ void set_is_using_ssl(bool ssl_on) { using_ssl_ = ssl_on; }
+
+ // Return the SpdyStream object associated with this slave connection.
+ // Note that this may be NULL in case mod_spdy is acting on behalf of
+ // another module. Not owned.
+ SpdyStream* slave_stream() const { return slave_stream_; }
+ void set_slave_stream(SpdyStream* stream) { slave_stream_ = stream; }
+
+ // Return the SPDY version will be using, or SPDY_VERSION_NONE if we're not
+ // using SPDY.
+ spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+ void set_spdy_version(spdy::SpdyVersion version) { spdy_version_ = version; }
+
+ // See SlaveConnection documentation for description of these.
+ void SetOutputFilter(ap_filter_rec_t* handle, void* context);
+ void SetInputFilter(ap_filter_rec_t* handle, void* context);
+
+ ap_filter_rec_t* output_filter_handle() const {
+ return output_filter_handle_;
+ }
+
+ void* output_filter_context() const {
+ return output_filter_context_;
+ }
+
+ ap_filter_rec_t* input_filter_handle() const {
+ return input_filter_handle_;
+ }
+
+ void* input_filter_context() const {
+ return input_filter_context_;
+ }
+
+ private:
+ // These are used to properly inform modules running on slave connections
+ // on whether the connection should be treated as using SPDY and SSL.
+ bool using_ssl_;
+ spdy::SpdyVersion spdy_version_;
+
+ // Used for SPDY push.
+ SpdyStream* slave_stream_;
+
+ // Filters to attach. These are set by clients of SlaveConnection
+ // between creation and Run(), and read by mod_spdy's PreConnection hook,
+ // where they are installed in Apache's filter chains.
+ ap_filter_rec_t* output_filter_handle_;
+ void* output_filter_context_;
+
+ ap_filter_rec_t* input_filter_handle_;
+ void* input_filter_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(SlaveConnectionContext);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_SLAVE_CONNECTION_CONTEXT_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/sockaddr_util.h"
+
+#include <cstddef> // for ptrdiff_t
+#include <cstring>
+
+#include "apr_strings.h"
+
+namespace mod_spdy {
+
+apr_sockaddr_t* DeepCopySockAddr(const apr_sockaddr_t* in, apr_pool_t* pool) {
+ apr_sockaddr_t* out = static_cast<apr_sockaddr_t*>(
+ apr_palloc(pool, sizeof(apr_sockaddr_t)));
+ std::memcpy(out, in, sizeof(apr_sockaddr_t));
+ out->pool = pool;
+
+ if (in->hostname != NULL) {
+ out->hostname = apr_pstrdup(pool, in->hostname);
+ }
+
+ if (in->servname != NULL) {
+ out->servname = apr_pstrdup(pool, in->servname);
+ }
+
+ if (in->ipaddr_ptr != NULL) {
+ // ipaddr_ptr points inside the struct, towards the bits containing
+ // the actual IPv4/IPv6 address (e.g. to ->sa.sin.sin_addr or
+ // ->sa.sin6.sin6_addr). We point to the same offset in 'out' as was used
+ // in 'in'.
+ ptrdiff_t ipaddr_ptr_offset =
+ static_cast<char*>(in->ipaddr_ptr) - reinterpret_cast<const char*>(in);
+ out->ipaddr_ptr = reinterpret_cast<char*>(out) + ipaddr_ptr_offset;
+ }
+
+ if (in->next != NULL) {
+ out->next = DeepCopySockAddr(in->next, pool);
+ }
+
+ return out;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// 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.
+
+#ifndef MOD_SPDY_APACHE_SOCKADDR_UTIL_H_
+#define MOD_SPDY_APACHE_SOCKADDR_UTIL_H_
+
+#include "apr_pools.h"
+#include "apr_network_io.h"
+
+namespace mod_spdy {
+
+// Deep-copies the apr_sockaddr_t 'in', with the result being allocated in the
+// pool 'pool'.
+apr_sockaddr_t* DeepCopySockAddr(const apr_sockaddr_t* in, apr_pool_t* pool);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_SOCKADDR_UTIL_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/sockaddr_util.h"
+
+#include "apr_strings.h"
+
+#include "base/basictypes.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void VerifySameAddr(apr_sockaddr_t* exp, apr_sockaddr_t* actual) {
+ // apr_sockaddr_equal checks the actual IP (4 or 6) portion of the address,
+ // and nothing else.
+ EXPECT_NE(0, apr_sockaddr_equal(exp, actual));
+
+ // Annoyingly this means we have to touch other fields directly.
+ EXPECT_STREQ(exp->hostname, actual->hostname);
+ EXPECT_STREQ(exp->servname, actual->servname);
+ EXPECT_EQ(exp->port, actual->port);
+ EXPECT_EQ(exp->salen, actual->salen);
+ EXPECT_EQ(exp->ipaddr_len, actual->ipaddr_len);
+ EXPECT_EQ(exp->addr_str_len, actual->addr_str_len);
+
+ // next fields must both be either null or non-null.
+ EXPECT_TRUE((exp->next == NULL) == (actual->next == NULL));
+ if (exp->next != NULL) {
+ VerifySameAddr(exp->next, actual->next);
+ }
+}
+
+TEST(SockAddrUtilTest, CloneIpv4) {
+ mod_spdy::LocalPool local, other;
+
+ apr_sockaddr_t* original = NULL;
+ ASSERT_EQ(APR_SUCCESS,
+ apr_sockaddr_info_get(
+ &original, "127.1.2.3", APR_INET, 80, 0, local.pool()));
+ original->hostname = apr_pstrdup(local.pool(), "localhost");
+ original->servname = apr_pstrdup(local.pool(), "http");
+
+ apr_sockaddr_t* clone = mod_spdy::DeepCopySockAddr(original, other.pool());
+ EXPECT_EQ(other.pool(), clone->pool);
+ VerifySameAddr(original, clone);
+}
+
+TEST(SockAddrUtilTest, CloneIpv6) {
+ mod_spdy::LocalPool local, other;
+
+ // The IPv6 address below was that of example.com on 2012-07-20.
+ apr_sockaddr_t* original = NULL;
+ ASSERT_EQ(APR_SUCCESS,
+ apr_sockaddr_info_get(
+ &original, "2001:500:88:200::10", APR_INET6,
+ 443, 0, local.pool()));
+ original->hostname = apr_pstrdup(local.pool(), "example.com");
+ original->servname = apr_pstrdup(local.pool(), "https");
+
+ apr_sockaddr_t* clone = mod_spdy::DeepCopySockAddr(original, other.pool());
+ EXPECT_EQ(other.pool(), clone->pool);
+ VerifySameAddr(original, clone);
+}
+
+TEST(SockAddrUtilTest, Clone2Records) {
+ // Test where ->next links an IpV4 record from IPv6 one.
+ mod_spdy::LocalPool local, other;
+
+ // Both addresses are of example.com as of 2012-07-20.
+ apr_sockaddr_t* original = NULL;
+ ASSERT_EQ(APR_SUCCESS,
+ apr_sockaddr_info_get(
+ &original, "2001:500:88:200::10", APR_INET6,
+ 443, 0, local.pool()));
+ original->hostname = apr_pstrdup(local.pool(), "example.com");
+ original->servname = apr_pstrdup(local.pool(), "https");
+
+ apr_sockaddr_t* original4 = NULL;
+ ASSERT_EQ(APR_SUCCESS,
+ apr_sockaddr_info_get(
+ &original4, "192.0.43.10", APR_INET,
+ 443, 0, local.pool()));
+ original4->hostname = apr_pstrdup(local.pool(), "example.com");
+ original4->servname = apr_pstrdup(local.pool(), "https");
+ original->next = original4;
+
+ apr_sockaddr_t* clone = mod_spdy::DeepCopySockAddr(original, other.pool());
+ EXPECT_EQ(other.pool(), clone->pool);
+ VerifySameAddr(original, clone);
+}
+
+} // namespace
--- /dev/null
+// 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.
+
+#include "mod_spdy/apache/ssl_util.h"
+
+#include "apr_optional.h"
+#include "apr_optional_hooks.h"
+
+#include "base/logging.h"
+
+// This file contains some utility functions for communicating to mod_ssl.
+
+// Declaring mod_ssl's optional hooks here (so that we don't need mod_ssl.h).
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec*));
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec*));
+
+namespace mod_spdy {
+
+namespace {
+
+// These global variables store pointers to "optional functions" defined in
+// mod_ssl. See TAMB 10.1.2 for more about optional functions. These are
+// assigned just once, at start-up, so concurrency is not an issue.
+int (*gDisableSslForConnection)(conn_rec*) = NULL;
+int (*gIsUsingSslForConnection)(conn_rec*) = NULL;
+
+} // namespace
+
+void RetrieveModSslFunctions() {
+ gDisableSslForConnection = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
+ gIsUsingSslForConnection = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ // If mod_ssl isn't installed, we'll get back NULL for these functions. Our
+ // other hook functions will fail gracefully (i.e. do nothing) if these
+ // functions are NULL, but if the user installed mod_spdy without mod_ssl and
+ // expected it to do anything, we should warn them otherwise.
+ //
+ // Note: Alternatively, it may be that there's no mod_ssl, but mod_spdy has
+ // been configured to assume SPDY for non-SSL connections, in which case this
+ // warning is untrue. But there's no easy way to check the server config
+ // from here, and normal users should never use that config option anyway
+ // (it's for debugging), so I don't think the spurious warning is a big deal.
+ if (gDisableSslForConnection == NULL &&
+ gIsUsingSslForConnection == NULL) {
+ LOG(WARNING) << "It seems that mod_spdy is installed but mod_ssl isn't. "
+ << "Without SSL, the server cannot ever use SPDY.";
+ }
+ // Whether or not mod_ssl is installed, either both functions should be
+ // non-NULL or both functions should be NULL. Otherwise, something is wrong
+ // (like, maybe some kind of bizarre mutant mod_ssl is installed) and
+ // mod_spdy probably won't work correctly.
+ if ((gDisableSslForConnection == NULL) ^
+ (gIsUsingSslForConnection == NULL)) {
+ LOG(DFATAL) << "Some, but not all, of mod_ssl's optional functions are "
+ << "available. What's going on?";
+ }
+}
+
+bool DisableSslForConnection(conn_rec* connection) {
+ return (gDisableSslForConnection != NULL) &&
+ (gDisableSslForConnection(connection) != 0);
+}
+
+bool IsUsingSslForConnection(conn_rec* connection) {
+ return (gIsUsingSslForConnection != NULL) &&
+ (gIsUsingSslForConnection(connection) != 0);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// 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.
+
+#ifndef MOD_SPDY_APACHE_SSL_UTIL_H_
+#define MOD_SPDY_APACHE_SSL_UTIL_H_
+
+// This file contains some utility functions for communicating to mod_ssl.
+
+struct conn_rec;
+
+namespace mod_spdy {
+
+// This must be called from optional_fn_retrieve hook at startup before
+// using any of the methods below.
+void RetrieveModSslFunctions();
+
+// Disables SSL on a connection. Returns true if successful, false if failed
+// for some reason (such as the optional function not being available, or
+// mod_ssl indicating a problem).
+bool DisableSslForConnection(conn_rec* connection);
+
+// Returns true if the given connection is using SSL. Note that this answers
+// whether the connection is really using SSL rather than whether we should tell
+// others that it is. This distinction matters for slave connection belonging to
+// an SSL master --- we're not really using SSL for them (so this method
+// will return false), but will claim we are (since they'll be encrypted once
+// going to other outside world).
+bool IsUsingSslForConnection(conn_rec* connection);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_SSL_UTIL_H_
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "httpd.h"
+#include "apr_buckets.h"
+#include "util_filter.h"
+
+// For unit tests, we don't link in Apache's util_filter.c, which defines the
+// below functions. To make our lives easier, we define dummy versions of them
+// here that simply report success.
+
+extern "C" {
+
+AP_DECLARE(apr_status_t) ap_pass_brigade(
+ ap_filter_t* filter, apr_bucket_brigade* bucket) {
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(void) ap_remove_output_filter(ap_filter_t* filter) {}
+
+} // extern "C"
--- /dev/null
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include <iostream>
+
+#include "apr_general.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Class to ensure that APR gets initialized and torn down.
+class AprInitializer {
+ public:
+ AprInitializer() {
+ const apr_status_t status = apr_initialize();
+ CHECK(status == APR_SUCCESS) << "APR initialization failed.";
+ }
+ ~AprInitializer() {
+ apr_terminate();
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AprInitializer);
+};
+
+} // namespace
+
+int main(int argc, char **argv) {
+ std::cout << "Running main() from spdy_apache_test_main.cc\n";
+ testing::InitGoogleTest(&argc, argv);
+ AprInitializer apr_initializer;
+ return RUN_ALL_TESTS();
+}
--- /dev/null
+MAJOR=0
+MINOR=1
+BUILD=0
+PATCH=0
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/executor.h"
+
+namespace mod_spdy {
+
+Executor::Executor() {}
+
+Executor::~Executor() {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_EXECUTOR_H_
+#define MOD_SPDY_COMMON_EXECUTOR_H_
+
+#include "base/basictypes.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net_instaweb { class Function; }
+
+namespace mod_spdy {
+
+// An interface for a service that can execute tasks. A thread pool (using
+// net_instaweb::QueuedWorkerPool or an apr_thread_pool_t) would be one obvious
+// implementation. In the future we may want to adjust this interface for use
+// in an event-driven environment (e.g. Nginx).
+class Executor {
+ public:
+ Executor();
+ virtual ~Executor();
+
+ // Add a new task to be run; the executor takes ownership of the task. The
+ // priority argument hints at how important this task is to get done, but the
+ // executor is free to ignore it. If Stop has already been called, the
+ // executor may immediately cancel the task rather than running it.
+ virtual void AddTask(net_instaweb::Function* task,
+ net::SpdyPriority priority) = 0;
+
+ // Stop the executor. Cancel all tasks that were pushed onto this executor
+ // but that have not yet begun to run. Tasks that were already running will
+ // continue to run, and this function must block until they have completed.
+ // It must be safe to call this method more than once.
+ virtual void Stop() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Executor);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_EXECUTOR_H_
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/common/http_request_visitor_interface.h"
+
+namespace mod_spdy {
+
+HttpRequestVisitorInterface::HttpRequestVisitorInterface() {}
+HttpRequestVisitorInterface::~HttpRequestVisitorInterface() {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_COMMON_HTTP_REQUEST_VISITOR_INTERFACE_H_
+#define MOD_SPDY_COMMON_HTTP_REQUEST_VISITOR_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+
+namespace mod_spdy {
+
+// Interface that gets called back as an HTTP stream is visited.
+class HttpRequestVisitorInterface {
+ public:
+ HttpRequestVisitorInterface();
+ virtual ~HttpRequestVisitorInterface();
+
+ // Called when an HTTP request line is visited. Indicates that a new HTTP
+ // request is being visited.
+ virtual void OnRequestLine(const base::StringPiece& method,
+ const base::StringPiece& path,
+ const base::StringPiece& version) = 0;
+
+ // Called zero or more times, once for each leading (i.e. normal, not
+ // trailing) HTTP header. This is called after OnRequestLine but before
+ // OnLeadingHeadersComplete.
+ virtual void OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) = 0;
+
+ // Called after the leading HTTP headers have been visited. This will be
+ // called exactly once when the leading headers are done (even if there were
+ // no leading headers).
+ virtual void OnLeadingHeadersComplete() = 0;
+
+ // Called zero or more times, after OnLeadingHeadersComplete. This method is
+ // mutually exclusive with OnDataChunk and OnDataChunksComplete; either data
+ // will be raw or chunked, but not both. If raw data is used, there cannot
+ // be trailing headers; the raw data section will be terminated by the call
+ // to OnComplete.
+ virtual void OnRawData(const base::StringPiece& data) = 0;
+
+ // Called zero or more times, after OnLeadingHeadersComplete, once for each
+ // "chunk" of the HTTP body. This method is mutually exclusive with
+ // OnRawData; either data will be raw or chunked, but not both.
+ virtual void OnDataChunk(const base::StringPiece& data) = 0;
+
+ // Called when there will be no more data chunks. There may still be
+ // trailing headers, however. This method is mutually exclusive with
+ // OnRawData; either data will be raw or chunked, but not both.
+ virtual void OnDataChunksComplete() = 0;
+
+ // Called zero or more times, once for each trailing header. This is called
+ // after OnDataChunksComplete but before OnTrailingHeadersComplete. It
+ // cannot be called if OnRawData was used.
+ virtual void OnTrailingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) = 0;
+
+ // Called after all the trailing HTTP headers have been visited. If there
+ // were any trailing headers, this will definitely be called; if there were
+ // no trailing headers, it is optional.
+ virtual void OnTrailingHeadersComplete() = 0;
+
+ // Called once when the HTTP request is totally done. This is called
+ // immediately after one of OnLeadingHeadersComplete, OnRawData,
+ // OnDataChunksComplete, or OnTrailingHeadersComplete. After this, no more
+ // methods will be called.
+ virtual void OnComplete() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpRequestVisitorInterface);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_REQUEST_VISITOR_INTERFACE_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/http_response_parser.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "mod_spdy/common/http_response_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace {
+
+// If the given position in the string is npos, then return the position of the
+// end of the string; otherwise, return the given position unchanged.
+size_t NposToEnd(const base::StringPiece& str, size_t pos) {
+ return pos == base::StringPiece::npos ? str.size() : pos;
+}
+
+} // namespace
+
+namespace mod_spdy {
+
+HttpResponseParser::HttpResponseParser(HttpResponseVisitorInterface* visitor)
+ : visitor_(visitor),
+ state_(STATUS_LINE),
+ body_type_(NO_BODY),
+ remaining_bytes_(0) {}
+
+HttpResponseParser::~HttpResponseParser() {}
+
+bool HttpResponseParser::ProcessInput(const base::StringPiece& input_data) {
+ // Keep track of the slice of data we are currently looking at. We will
+ // modify this variable as we go.
+ base::StringPiece data = input_data;
+
+ size_t last_length = data.size() + 1;
+ while (!data.empty()) {
+ // Safety check to avoid infinite loops in case our code is buggy; the
+ // slice of data we are looking at should get strictly smaller on every
+ // iteration of this loop.
+ if (data.size() >= last_length) {
+ LOG(DFATAL) << "Potential infinite loop.";
+ return false;
+ }
+ last_length = data.size();
+
+ // Process the data based on our current parser state. Most of these
+ // methods receive a pointer to `data` and will mutate it as they consume
+ // bytes. We continue looping until the whole input_data is consumed.
+ switch (state_) {
+ case STATUS_LINE:
+ if (!ProcessStatusLine(&data)) {
+ return false;
+ }
+ break;
+ case LEADING_HEADERS_CHECK_NEXT_LINE:
+ if (!CheckStartOfHeaderLine(data)) {
+ return false;
+ }
+ // fallthrough
+ case LEADING_HEADERS:
+ if (!ProcessLeadingHeaders(&data)) {
+ return false;
+ }
+ break;
+ case CHUNK_START:
+ if (!ProcessChunkStart(&data)) {
+ return false;
+ }
+ break;
+ case BODY_DATA:
+ if (!ProcessBodyData(&data)) {
+ return false;
+ }
+ break;
+ case CHUNK_ENDING:
+ if (!ProcessChunkEnding(&data)) {
+ return false;
+ }
+ break;
+ case COMPLETE:
+ DCHECK(buffer_.empty());
+ return true;
+ default:
+ LOG(DFATAL) << "Invalid parser state: " << state_;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool HttpResponseParser::ProcessStatusLine(base::StringPiece* data) {
+ DCHECK(state_ == STATUS_LINE);
+ const size_t linebreak = data->find("\r\n");
+
+ // If we haven't reached the end of the line yet, buffer the data and quit.
+ if (linebreak == base::StringPiece::npos) {
+ data->AppendToString(&buffer_);
+ *data = base::StringPiece();
+ return true;
+ }
+
+ // Combine the data up to the linebreak with what we've buffered, and parse
+ // the status line out of it.
+ data->substr(0, linebreak).AppendToString(&buffer_);
+ if (!ParseStatusLine(buffer_)) {
+ return false;
+ }
+ buffer_.clear();
+
+ // Chop off the linebreak and all data before it, and move on to parsing the
+ // leading headers.
+ *data = data->substr(linebreak + 2);
+ state_ = LEADING_HEADERS;
+ return true;
+}
+
+bool HttpResponseParser::CheckStartOfHeaderLine(const base::StringPiece& data){
+ // This state is for when we have a complete header line buffered, and we
+ // need to check the next line to see if it starts with leading whitespace.
+ DCHECK(state_ == LEADING_HEADERS_CHECK_NEXT_LINE);
+ DCHECK(!buffer_.empty());
+ DCHECK(!data.empty());
+
+ // If this line _doesn't_ start with whitespace, then the buffered line is a
+ // complete header line, so we should parse it and clear the buffer.
+ // Otherwise, this next line is a continuation of the header, so we need to
+ // buffer more data.
+ const char first = data[0];
+ if (first != ' ' && first != '\t') {
+ if (!ParseLeadingHeader(buffer_)) {
+ return false;
+ }
+ buffer_.clear();
+ }
+
+ // Either way, we're ready to continuing parsing headers.
+ state_ = LEADING_HEADERS;
+ return true;
+}
+
+bool HttpResponseParser::ProcessLeadingHeaders(base::StringPiece* data) {
+ DCHECK(state_ == LEADING_HEADERS);
+ const size_t linebreak = data->find("\r\n");
+
+ // If we haven't reached the end of the line yet, buffer the data and quit.
+ if (linebreak == base::StringPiece::npos) {
+ data->AppendToString(&buffer_);
+ *data = base::StringPiece();
+ return true;
+ }
+
+ // If we're not in the middle of a header line (buffer is empty) and the
+ // linebreak comes at the very beginning, this must be the blank line that
+ // signals the end of the leading headers. Skip the linebreak, switch states
+ // depending on what headers we saw (Is there body data? Is it chunked?),
+ // and return.
+ if (linebreak == 0 && buffer_.empty()) {
+ switch (body_type_) {
+ case CHUNKED_BODY:
+ state_ = CHUNK_START;
+ break;
+ case UNCHUNKED_BODY:
+ state_ = BODY_DATA;
+ break;
+ case NO_BODY:
+ state_ = COMPLETE;
+ break;
+ default:
+ LOG(DFATAL) << "Invalid body type: " << body_type_;
+ return false;
+ }
+ visitor_->OnLeadingHeadersComplete(state_ == COMPLETE);
+ *data = data->substr(linebreak + 2);
+ return true;
+ }
+
+ // We've reached the end of the line, but we need to check the next line to
+ // see if it's a continuation of this header. Buffer up to the linebreak,
+ // skip the linebreak itself, and set our state to check the next line.
+ data->substr(0, linebreak).AppendToString(&buffer_);
+ *data = data->substr(linebreak + 2);
+ state_ = LEADING_HEADERS_CHECK_NEXT_LINE;
+ return true;
+}
+
+bool HttpResponseParser::ProcessChunkStart(base::StringPiece* data) {
+ DCHECK(state_ == CHUNK_START);
+ const size_t linebreak = data->find("\r\n");
+
+ // If we haven't reached the end of the line yet, buffer the data and quit.
+ if (linebreak == base::StringPiece::npos) {
+ data->AppendToString(&buffer_);
+ *data = base::StringPiece();
+ return true;
+ }
+
+ // Combine the data up to the linebreak with what we've buffered, and parse
+ // the chunk length out of it.
+ data->substr(0, linebreak).AppendToString(&buffer_);
+ if (!ParseChunkStart(buffer_)) {
+ return false;
+ }
+ buffer_.clear();
+
+ // Skip the linebreak.
+ *data = data->substr(linebreak + 2);
+
+ // ParseChunkStart will put the size of the chunk into remaining_bytes_. If
+ // the chunk size is zero, that means we've reached the end of the body data.
+ // Otherwise, we now need to read the data in this chunk.
+ if (remaining_bytes_ == 0) {
+ state_ = COMPLETE;
+ visitor_->OnData(base::StringPiece(), true);
+ } else {
+ state_ = BODY_DATA;
+ }
+ return true;
+}
+
+bool HttpResponseParser::ProcessBodyData(base::StringPiece* data) {
+ DCHECK(state_ == BODY_DATA);
+
+ // We never buffer anything when reading the body data. This minimizes how
+ // much string copying we need to do for most responses.
+ DCHECK(buffer_.empty());
+
+ // If the available data is less that what remains of this chunk (if the data
+ // is chunked) or of the whole body (if there was instead an explicit
+ // content-length), then read in all the data we have and subtract from
+ // remaining_bytes_.
+ if (data->size() < remaining_bytes_) {
+ visitor_->OnData(*data, false);
+ remaining_bytes_ -= data->size();
+ *data = base::StringPiece();
+ }
+ // Otherwise, we have enough data here to fulfill remaining_bytes_, so read
+ // in that much data, and then switch states depending on whether we're using
+ // chunking or not.
+ else {
+ if (body_type_ == CHUNKED_BODY) {
+ state_ = CHUNK_ENDING;
+ } else {
+ DCHECK(body_type_ == UNCHUNKED_BODY);
+ state_ = COMPLETE;
+ }
+ visitor_->OnData(data->substr(0, remaining_bytes_), state_ == COMPLETE);
+ *data = data->substr(remaining_bytes_);
+ remaining_bytes_ = 0;
+ }
+ return true;
+}
+
+bool HttpResponseParser::ProcessChunkEnding(base::StringPiece* data) {
+ DCHECK(state_ == CHUNK_ENDING);
+ // For whatever reason, HTTP requires each chunk to end with a CRLF. So,
+ // make sure it's there, and then skip it, before moving on to read the next
+ // chunk.
+ if (!data->starts_with("\r\n")) {
+ VLOG(1) << "Expected CRLF at end of chunk.";
+ return false;
+ }
+ *data = data->substr(2);
+ state_ = CHUNK_START;
+ return true;
+}
+
+bool HttpResponseParser::ParseStatusLine(const base::StringPiece& text) {
+ // An HTTP status line should look like:
+ // <HTTP version> <single space> <status code> <single space> <status phrase>
+ // For example, "HTTP/1.1 301 Moved permenantly".
+ // We'll be a little more lenient just in case, and allow multiple spaces
+ // between each part, and allow the phrase to be omitted.
+ const size_t first_space = text.find(' ');
+ if (first_space == base::StringPiece::npos) {
+ VLOG(1) << "Bad status line: " << text;
+ return false;
+ }
+ const size_t start_of_code = text.find_first_not_of(' ', first_space);
+ if (start_of_code == base::StringPiece::npos) {
+ VLOG(1) << "Bad status line: " << text;
+ return false;
+ }
+ const size_t second_space = NposToEnd(text, text.find(' ', start_of_code));
+ const size_t start_of_phrase =
+ NposToEnd(text, text.find_first_not_of(' ', second_space));
+
+ visitor_->OnStatusLine(
+ text.substr(0, first_space),
+ text.substr(start_of_code, second_space - start_of_code),
+ text.substr(start_of_phrase));
+ return true;
+}
+
+bool HttpResponseParser::ParseLeadingHeader(const base::StringPiece& text) {
+ // Even for multiline headers, we strip out the CRLFs, so there shouldn't be
+ // any left in the text that we're parsing.
+ DCHECK(text.find("\r\n") == base::StringPiece::npos);
+
+ // Find the colon separating the key from the value, and skip any leading
+ // whitespace between the colon and the value.
+ const size_t colon = text.find(':');
+ if (colon == base::StringPiece::npos) {
+ VLOG(1) << "Bad header line: " << text;
+ return false;
+ }
+ const size_t value_start =
+ NposToEnd(text, text.find_first_not_of(" \t", colon + 1));
+
+ const base::StringPiece key = text.substr(0, colon);
+ const base::StringPiece value = text.substr(value_start);
+
+ // We need to check the Content-Length and Transfer-Encoding headers to know
+ // if we're using chunking, and if not, how long the body is.
+ if (LowerCaseEqualsASCII(key.begin(), key.end(), http::kTransferEncoding)) {
+ if (value == http::kChunked) {
+ body_type_ = CHUNKED_BODY;
+ }
+ } else if (body_type_ != CHUNKED_BODY &&
+ LowerCaseEqualsASCII(key.begin(), key.end(),
+ http::kContentLength)) {
+ uint64 uint_value = 0u;
+ if (base::StringToUint64(value, &uint_value) && uint_value > 0u) {
+ remaining_bytes_ = uint_value;
+ body_type_ = UNCHUNKED_BODY;
+ } else {
+ VLOG(1) << "Bad content-length: " << value;
+ }
+ }
+
+ visitor_->OnLeadingHeader(key, value);
+ return true;
+}
+
+bool HttpResponseParser::ParseChunkStart(const base::StringPiece& text) {
+ // The line at the start of each chunk consists of the chunk length in
+ // hexadecimal, potentially followed by chunk-extension metadata that we
+ // don't care about anyway. So just parse out the hex number and ignore the
+ // rest.
+ const size_t length =
+ NposToEnd(text, text.find_first_not_of("0123456789abcdefABCDEF"));
+ int int_value = 0;
+ if (!base::HexStringToInt(text.substr(0, length), &int_value) ||
+ int_value < 0) {
+ VLOG(1) << "Bad chunk line: " << text;
+ return false;
+ }
+ remaining_bytes_ = static_cast<size_t>(int_value);
+ return true;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_HTTP_RESPONSE_PARSER_H_
+#define MOD_SPDY_COMMON_HTTP_RESPONSE_PARSER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+
+namespace mod_spdy {
+
+class HttpResponseVisitorInterface;
+
+// Parses incoming HTTP response data. Data is fed in piece by piece with the
+// ProcessInput method, and appropriate methods are called on the visitor.
+// There is no need to indicate the end of the input, as this is inferred from
+// the Content-Length or Transfer-Encoding headers. If the response uses
+// chunked encoding, the parser will de-chunk it. Note that all data after the
+// end of the response body, including trailing headers, will be completely
+// ignored.
+class HttpResponseParser {
+ public:
+ explicit HttpResponseParser(HttpResponseVisitorInterface* visitor);
+ ~HttpResponseParser();
+
+ // Return true on success, false on failure.
+ bool ProcessInput(const base::StringPiece& input_data);
+ bool ProcessInput(const char* data, size_t size) {
+ return ProcessInput(base::StringPiece(data, size));
+ }
+
+ // For unit testing only: Get the remaining number of bytes expected (in the
+ // whole response, if we used Content-Length, or just in the current chunk,
+ // if we used Transfer-Encoding: chunked).
+ uint64 GetRemainingBytesForTest() const { return remaining_bytes_; }
+
+ private:
+ enum ParserState {
+ STATUS_LINE,
+ LEADING_HEADERS,
+ LEADING_HEADERS_CHECK_NEXT_LINE,
+ CHUNK_START,
+ BODY_DATA,
+ CHUNK_ENDING,
+ COMPLETE
+ };
+
+ enum BodyType {
+ NO_BODY,
+ UNCHUNKED_BODY,
+ CHUNKED_BODY
+ };
+
+ bool ProcessStatusLine(base::StringPiece* data);
+ bool CheckStartOfHeaderLine(const base::StringPiece& data);
+ bool ProcessLeadingHeaders(base::StringPiece* data);
+ bool ProcessChunkStart(base::StringPiece* data);
+ bool ProcessBodyData(base::StringPiece* data);
+ bool ProcessChunkEnding(base::StringPiece* data);
+
+ bool ParseStatusLine(const base::StringPiece& text);
+ bool ParseLeadingHeader(const base::StringPiece& text);
+ bool ParseChunkStart(const base::StringPiece& text);
+
+ HttpResponseVisitorInterface* const visitor_;
+ ParserState state_;
+ BodyType body_type_;
+ uint64 remaining_bytes_;
+ std::string buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponseParser);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_RESPONSE_PARSER_H_
--- /dev/null
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/http_response_parser.h"
+
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_response_visitor_interface.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using mod_spdy::HttpResponseParser;
+using testing::Eq;
+using testing::InSequence;
+
+namespace {
+
+class MockHttpResponseVisitor: public mod_spdy::HttpResponseVisitorInterface {
+ public:
+ MOCK_METHOD3(OnStatusLine, void(const base::StringPiece&,
+ const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD2(OnLeadingHeader, void(const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD1(OnLeadingHeadersComplete, void(bool));
+ MOCK_METHOD2(OnData, void(const base::StringPiece&, bool));
+};
+
+class HttpResponseParserTest : public testing::Test {
+ public:
+ HttpResponseParserTest() : parser_(&visitor_) {}
+
+ protected:
+ MockHttpResponseVisitor visitor_;
+ HttpResponseParser parser_;
+};
+
+TEST_F(HttpResponseParserTest, SimpleWithContentLength) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Length"), Eq("14")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Type"), Eq("text/plain")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("X-Whatever"), Eq("foobar")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 14\r\n"
+ "Content-Type: text/plain\r\n"
+ "X-Whatever:foobar\r\n"
+ "\r\n"
+ "Hello, world!\n"
+ "\r\n"));
+}
+
+TEST_F(HttpResponseParserTest, SimpleWithChunkedData) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("Transfer-Encoding"), Eq("chunked")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Type"), Eq("text/plain")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("Hello, world!\n"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("It sure is good to see you today."),
+ Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(""), Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "E\r\n"
+ "Hello, world!\n\r\n"
+ "21; some-random-chunk-extension\r\n"
+ "It sure is good to see you today.\r\n"
+ "0\r\n"
+ "\r\n"));
+}
+
+// Check that Transfer-Encoding: chunked supersedes Content-Length.
+TEST_F(HttpResponseParserTest, ContentLengthAndTransferEncoding) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Length"), Eq("3")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("Transfer-Encoding"), Eq("chunked")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("Hello,"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(" world!\n"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(""), Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 3\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "6\r\n"
+ "Hello,\r\n"
+ "8\r\n"
+ " world!\n\r\n"
+ "0\r\n"
+ "\r\n"));
+}
+
+// Check that Transfer-Encoding: chunked supersedes Content-Length even if
+// Content-Length comes later.
+TEST_F(HttpResponseParserTest, TransferEncodingAndContentLength) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("Transfer-Encoding"), Eq("chunked")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Length"), Eq("3")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("Hello,"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(" world!\n"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(""), Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Content-Length: 3\r\n"
+ "\r\n"
+ "6\r\n"
+ "Hello,\r\n"
+ "8\r\n"
+ " world!\n\r\n"
+ "0\r\n"
+ "\r\n"));
+}
+
+TEST_F(HttpResponseParserTest, NoBodyData) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("301"),
+ Eq("Moved permenantly")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("X-Empty"), Eq("")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Location"), Eq("/foo/bar.html")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 301 Moved permenantly\r\n"
+ "X-Empty:\r\n"
+ "Location: /foo/bar.html\r\n"
+ "\r\n"));
+}
+
+TEST_F(HttpResponseParserTest, NoStatusPhrase) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("123"), Eq("")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 123\r\n"
+ "\r\n"));
+}
+
+TEST_F(HttpResponseParserTest, HeadersBrokenAcrossLines) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("301"),
+ Eq("Moved permenantly")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("X-NextLine"), Eq("Alas, this is legal HTTP.")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Location"), Eq("/foo/bar.html")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("X-ThreeLines"), Eq("foo bar baz quux")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 301 Moved permenantly\r\n"
+ "X-NextLine:\r\n"
+ "\tAlas, this is legal HTTP.\r\n"
+ "Location: /foo/bar.html\r\n"
+ "X-ThreeLines: foo\r\n"
+ " bar baz \r\n"
+ " quux\r\n"
+ "\r\n"));
+}
+
+TEST_F(HttpResponseParserTest, DividedUpIntoPieces) {
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("418"),
+ Eq("I'm a teapot")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("tRaNsFeR-EnCoDiNg"), Eq("chunked")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("X-TwoLines"), Eq("foo bar")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Type"), Eq("text/plain")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("Hello,"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(" world!\n"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("It sure"), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(" is good to see you today."), Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq(""), Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput("HTTP/1.1 418 I'm"));
+ ASSERT_TRUE(parser_.ProcessInput(" a teapot\r\ntRaNsFeR-EnCoDiNg:"));
+ ASSERT_TRUE(parser_.ProcessInput("chunked\r\n"));
+ ASSERT_TRUE(parser_.ProcessInput("X-TwoLines:\tfoo "));
+ ASSERT_TRUE(parser_.ProcessInput("\r\n"));
+ ASSERT_TRUE(parser_.ProcessInput(" bar"));
+ ASSERT_TRUE(parser_.ProcessInput("\r\nContent-Type: text/plain"));
+ ASSERT_TRUE(parser_.ProcessInput("\r\n\r\nE"));
+ ASSERT_TRUE(parser_.ProcessInput("\r\n"));
+ ASSERT_TRUE(parser_.ProcessInput("Hello,"));
+ ASSERT_TRUE(parser_.ProcessInput(" world!\n"));
+ ASSERT_TRUE(parser_.ProcessInput("\r\n"));
+ ASSERT_TRUE(parser_.ProcessInput("21; some-random-"));
+ ASSERT_TRUE(parser_.ProcessInput("chunk-extension\r\nIt sure"));
+ ASSERT_TRUE(parser_.ProcessInput(" is good to see you today.\r\n0"));
+ ASSERT_TRUE(parser_.ProcessInput("0\r\n\r\n"));
+}
+
+// Test that we gracefully handle bogus content-lengths. We should effectively
+// ignore the header, assume that the response has no content, and ignore what
+// follows the headers.
+TEST_F(HttpResponseParserTest, BogusContentLength) {
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("Content-Length"), Eq("bogus")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: bogus\r\n"
+ "\r\n"
+ "bogus bogus bogus\r\n"));
+}
+
+// Test that we gracefully handle over-large content-lengths. As with
+// unparsable Content-Length values, an overflow of the Content-Length value
+// should result in us ignoring it.
+TEST_F(HttpResponseParserTest, ContentLengthOverflow) {
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("Content-Length"), Eq("9999999999999999999999999999999999999")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(true)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 9999999999999999999999999999999999999\r\n"
+ "\r\n"
+ "bogus bogus bogus\r\n"));
+}
+
+// We should be able to handle pretty big content lengths without overflowing,
+// however! Otherwise, downloads of extremely large files may fail. Here, we
+// test (the beginning of) a response that will contain a 5TB of data.
+TEST_F(HttpResponseParserTest, LargeContentLength) {
+ EXPECT_CALL(visitor_, OnStatusLine(Eq("HTTP/1.1"), Eq("200"), Eq("OK")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq("Content-Length"), Eq("5497558138880")));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete(Eq(false)));
+ EXPECT_CALL(visitor_, OnData(Eq("This is the beginning of 5TB of data."),
+ Eq(false)));
+
+ ASSERT_TRUE(parser_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 5497558138880\r\n"
+ "\r\n"
+ "This is the beginning of 5TB of data."));
+ ASSERT_EQ(5497558138843uLL, parser_.GetRemainingBytesForTest());
+}
+
+} // namespace
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/common/http_response_visitor_interface.h"
+
+namespace mod_spdy {
+
+HttpResponseVisitorInterface::HttpResponseVisitorInterface() {}
+HttpResponseVisitorInterface::~HttpResponseVisitorInterface() {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_COMMON_HTTP_RESPONSE_VISITOR_INTERFACE_H_
+#define MOD_SPDY_COMMON_HTTP_RESPONSE_VISITOR_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+
+namespace mod_spdy {
+
+// Interface that gets called back as an HTTP response is visited.
+class HttpResponseVisitorInterface {
+ public:
+ HttpResponseVisitorInterface();
+ virtual ~HttpResponseVisitorInterface();
+
+ // Called when an HTTP response status line is visited. Indicates that a new
+ // HTTP response is being visited.
+ virtual void OnStatusLine(const base::StringPiece& version,
+ const base::StringPiece& status_code,
+ const base::StringPiece& status_phrase) = 0;
+
+ // Called zero or more times, once for each leading (i.e. normal, not
+ // trailing) HTTP header. This is called after OnStatusLine but before
+ // OnLeadingHeadersComplete.
+ virtual void OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) = 0;
+
+ // Called after the leading HTTP headers have been visited. This will be
+ // called exactly once when the leading headers are done (even if there were
+ // no leading headers). If the `fin` argument is true, the response is now
+ // complete (i.e. it has no body) and no more methods will be called.
+ virtual void OnLeadingHeadersComplete(bool fin) = 0;
+
+ // Called zero or more times, after OnLeadingHeadersComplete. If the `fin`
+ // argument is true, the response is now complete and no more methods will be
+ // called.
+ virtual void OnData(const base::StringPiece& data, bool fin) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpResponseVisitorInterface);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_RESPONSE_VISITOR_INTERFACE_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/common/http_string_builder.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+void OnHeader(const base::StringPiece& key,
+ const base::StringPiece& value,
+ std::string* output) {
+ key.AppendToString(output);
+ output->append(": ");
+ value.AppendToString(output);
+ output->append("\r\n");
+}
+
+} // namespace
+
+namespace mod_spdy {
+
+HttpStringBuilder::HttpStringBuilder(std::string* str)
+ : string_(str), state_(REQUEST_LINE) {
+ CHECK(string_);
+}
+
+HttpStringBuilder::~HttpStringBuilder() {}
+
+void HttpStringBuilder::OnRequestLine(const base::StringPiece& method,
+ const base::StringPiece& path,
+ const base::StringPiece& version) {
+ DCHECK(state_ == REQUEST_LINE);
+ state_ = LEADING_HEADERS;
+ method.AppendToString(string_);
+ string_->push_back(' ');
+ path.AppendToString(string_);
+ string_->push_back(' ');
+ version.AppendToString(string_);
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ DCHECK(state_ == LEADING_HEADERS);
+ OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnLeadingHeadersComplete() {
+ DCHECK(state_ == LEADING_HEADERS);
+ state_ = LEADING_HEADERS_COMPLETE;
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnRawData(const base::StringPiece& data) {
+ DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == RAW_DATA);
+ state_ = RAW_DATA;
+ data.AppendToString(string_);
+}
+
+void HttpStringBuilder::OnDataChunk(const base::StringPiece& data) {
+ DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == DATA_CHUNKS);
+ state_ = DATA_CHUNKS;
+ // Encode the data as an HTTP data chunk. See RFC 2616 section 3.6.1 for
+ // details.
+ base::StringAppendF(string_, "%lX\r\n",
+ static_cast<unsigned long>(data.size()));
+ data.AppendToString(string_);
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnDataChunksComplete() {
+ DCHECK(state_ == DATA_CHUNKS);
+ state_ = DATA_CHUNKS_COMPLETE;
+ // Indicate that there are no more HTTP data chunks coming. See RFC 2616
+ // section 3.6.1 for details.
+ string_->append("0\r\n");
+}
+
+void HttpStringBuilder::OnTrailingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ DCHECK(state_ == DATA_CHUNKS_COMPLETE || state_ == TRAILING_HEADERS);
+ state_ = TRAILING_HEADERS;
+ OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnTrailingHeadersComplete() {
+ DCHECK(state_ == TRAILING_HEADERS);
+ state_ = TRAILING_HEADERS_COMPLETE;
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnComplete() {
+ DCHECK(state_ == LEADING_HEADERS_COMPLETE ||
+ state_ == RAW_DATA ||
+ state_ == DATA_CHUNKS_COMPLETE ||
+ state_ == TRAILING_HEADERS_COMPLETE);
+ if (state_ == DATA_CHUNKS_COMPLETE) {
+ // In this case, there have been data chunks, but we haven't called
+ // OnTrailingHeadersComplete because there were no trailing headers. We
+ // still need an empty line to indicate the end of the request.
+ string_->append("\r\n");
+ }
+ state_ = COMPLETE;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// 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.
+
+#ifndef MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
+#define MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+
+namespace mod_spdy {
+
+// An HttpRequestVisitorInterface class that appends to a std::string.
+class HttpStringBuilder : public HttpRequestVisitorInterface {
+ public:
+ explicit HttpStringBuilder(std::string* str);
+ virtual ~HttpStringBuilder();
+
+ bool is_complete() const { return state_ == COMPLETE; }
+
+ // HttpRequestVisitorInterface methods:
+ virtual void OnRequestLine(const base::StringPiece& method,
+ const base::StringPiece& path,
+ const base::StringPiece& version);
+ virtual void OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+ virtual void OnLeadingHeadersComplete();
+ virtual void OnRawData(const base::StringPiece& data);
+ virtual void OnDataChunk(const base::StringPiece& data);
+ virtual void OnDataChunksComplete();
+ virtual void OnTrailingHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+ virtual void OnTrailingHeadersComplete();
+ virtual void OnComplete();
+
+ private:
+ enum State {
+ REQUEST_LINE,
+ LEADING_HEADERS,
+ LEADING_HEADERS_COMPLETE,
+ RAW_DATA,
+ DATA_CHUNKS,
+ DATA_CHUNKS_COMPLETE,
+ TRAILING_HEADERS,
+ TRAILING_HEADERS_COMPLETE,
+ COMPLETE
+ };
+
+ std::string* const string_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpStringBuilder);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_response_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// This is the number of bytes we want to send per data frame. We never send
+// data frames larger than this, but we might send smaller ones if we have to
+// flush early.
+// TODO The SPDY folks say that smallish (~4kB) data frames are good; however,
+// we should experiment later on to see what value here performs the best.
+const size_t kTargetDataFrameBytes = 4096;
+
+} // namespace
+
+namespace mod_spdy {
+
+class HttpToSpdyConverter::ConverterImpl : public HttpResponseVisitorInterface{
+ public:
+ ConverterImpl(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+ virtual ~ConverterImpl();
+
+ void Flush();
+
+ // HttpResponseVisitorInterface methods:
+ virtual void OnStatusLine(const base::StringPiece& version,
+ const base::StringPiece& status_code,
+ const base::StringPiece& status_phrase);
+ virtual void OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+ virtual void OnLeadingHeadersComplete(bool fin);
+ virtual void OnData(const base::StringPiece& data, bool fin);
+
+ private:
+ void SendDataIfNecessary(bool flush, bool fin);
+ void SendDataFrame(const char* data, size_t size, bool flag_fin);
+
+ const spdy::SpdyVersion spdy_version_;
+ SpdyReceiver* const receiver_;
+ net::SpdyHeaderBlock headers_;
+ std::string data_buffer_;
+ bool sent_flag_fin_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConverterImpl);
+};
+
+HttpToSpdyConverter::SpdyReceiver::SpdyReceiver() {}
+
+HttpToSpdyConverter::SpdyReceiver::~SpdyReceiver() {}
+
+HttpToSpdyConverter::HttpToSpdyConverter(spdy::SpdyVersion spdy_version,
+ SpdyReceiver* receiver)
+ : impl_(new ConverterImpl(spdy_version, receiver)),
+ parser_(impl_.get()) {}
+
+HttpToSpdyConverter::~HttpToSpdyConverter() {}
+
+bool HttpToSpdyConverter::ProcessInput(base::StringPiece input_data) {
+ return parser_.ProcessInput(input_data);
+}
+
+void HttpToSpdyConverter::Flush() {
+ impl_->Flush();
+}
+
+HttpToSpdyConverter::ConverterImpl::ConverterImpl(
+ spdy::SpdyVersion spdy_version, SpdyReceiver* receiver)
+ : spdy_version_(spdy_version),
+ receiver_(receiver),
+ sent_flag_fin_(false) {
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ CHECK(receiver_);
+}
+
+HttpToSpdyConverter::ConverterImpl::~ConverterImpl() {}
+
+void HttpToSpdyConverter::ConverterImpl::Flush() {
+ SendDataIfNecessary(true, // true = do flush
+ false); // false = not fin yet
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnStatusLine(
+ const base::StringPiece& version,
+ const base::StringPiece& status_code,
+ const base::StringPiece& status_phrase) {
+ DCHECK(headers_.empty());
+ const bool spdy2 = spdy_version_ < spdy::SPDY_VERSION_3;
+ headers_[spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version] =
+ version.as_string();
+ headers_[spdy2 ? spdy::kSpdy2Status : spdy::kSpdy3Status] =
+ status_code.as_string();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeader(
+ const base::StringPiece& key,
+ const base::StringPiece& value) {
+ // Filter out headers that are invalid in SPDY.
+ if (IsInvalidSpdyResponseHeader(key)) {
+ return;
+ }
+ MergeInHeader(key, value, &headers_);
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeadersComplete(bool fin) {
+ if (sent_flag_fin_) {
+ LOG(DFATAL) << "Trying to send headers after sending FLAG_FIN";
+ return;
+ }
+ if (fin) {
+ sent_flag_fin_ = true;
+ }
+ receiver_->ReceiveSynReply(&headers_, fin);
+ headers_.clear();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnData(const base::StringPiece& data,
+ bool fin) {
+ data.AppendToString(&data_buffer_);
+ SendDataIfNecessary(false, fin); // false = don't flush
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataIfNecessary(bool flush,
+ bool fin) {
+ // If we have (strictly) more than one frame's worth of data waiting, send it
+ // down the filter chain, kTargetDataFrameBytes bytes at a time. If we are
+ // left with _exactly_ kTargetDataFrameBytes bytes of data, we'll deal with
+ // that in the next code block (see the comment there to explain why).
+ if (data_buffer_.size() > kTargetDataFrameBytes) {
+ const char* start = data_buffer_.data();
+ size_t size = data_buffer_.size();
+ while (size > kTargetDataFrameBytes) {
+ SendDataFrame(start, kTargetDataFrameBytes, false);
+ start += kTargetDataFrameBytes;
+ size -= kTargetDataFrameBytes;
+ }
+ data_buffer_.erase(0, data_buffer_.size() - size);
+ }
+ DCHECK(data_buffer_.size() <= kTargetDataFrameBytes);
+
+ // We may still have some leftover data. We need to send another data frame
+ // now (rather than waiting for a full kTargetDataFrameBytes) if:
+ // 1) This is the end of the response,
+ // 2) we're supposed to flush and the buffer is nonempty, or
+ // 3) we still have a full data frame's worth in the buffer.
+ //
+ // Note that because of the previous code block, condition (3) will only be
+ // true if we have exactly kTargetDataFrameBytes of data. However, dealing
+ // with that case here instead of in the above block makes it easier to make
+ // sure we correctly set FLAG_FIN on the final data frame, which is why the
+ // above block uses a strict, > comparison rather than a non-strict, >=
+ // comparison.
+ if (fin || (flush && !data_buffer_.empty()) ||
+ data_buffer_.size() >= kTargetDataFrameBytes) {
+ SendDataFrame(data_buffer_.data(), data_buffer_.size(), fin);
+ data_buffer_.clear();
+ }
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataFrame(
+ const char* data, size_t size, bool flag_fin) {
+ if (sent_flag_fin_) {
+ LOG(DFATAL) << "Trying to send data after sending FLAG_FIN";
+ return;
+ }
+ if (flag_fin) {
+ sent_flag_fin_ = true;
+ }
+ receiver_->ReceiveData(base::StringPiece(data, size), flag_fin);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
+#define MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_response_parser.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h" // for SpdyHeaderBlock
+
+namespace mod_spdy {
+
+// Parses incoming HTTP response data and converts it into equivalent SPDY
+// frame data.
+class HttpToSpdyConverter {
+ public:
+ // Interface for the class that will receive frame data from the converter.
+ class SpdyReceiver {
+ public:
+ SpdyReceiver();
+ virtual ~SpdyReceiver();
+
+ // Receive a SYN_REPLY frame with the given headers. The callee is free to
+ // mutate the headers map (e.g. to add an extra header) before forwarding
+ // it on, but the pointer will not remain valid after this method returns.
+ virtual void ReceiveSynReply(net::SpdyHeaderBlock* headers,
+ bool flag_fin) = 0;
+
+ // Receive a DATA frame with the given payload. The data pointer will not
+ // remain valid after this method returns.
+ virtual void ReceiveData(base::StringPiece data, bool flag_fin) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyReceiver);
+ };
+
+ // Create a converter that will send frame data to the given receiver. The
+ // converter does *not* gain ownership of the receiver.
+ HttpToSpdyConverter(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+ ~HttpToSpdyConverter();
+
+ // Parse and process the next chunk of input; return true on success, false
+ // on failure.
+ bool ProcessInput(base::StringPiece input_data);
+ bool ProcessInput(const char* data, size_t size) {
+ return ProcessInput(base::StringPiece(data, size));
+ }
+
+ // Flush out any buffered data.
+ void Flush();
+
+ private:
+ class ConverterImpl;
+ scoped_ptr<ConverterImpl> impl_;
+ HttpResponseParser parser_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpToSpdyConverter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
--- /dev/null
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DeleteArg;
+using testing::Eq;
+using testing::InSequence;
+using testing::Pointee;
+
+namespace {
+
+class MockSpdyReceiver : public mod_spdy::HttpToSpdyConverter::SpdyReceiver {
+ public:
+ MOCK_METHOD2(ReceiveSynReply, void(net::SpdyHeaderBlock* headers,
+ bool flag_fin));
+ MOCK_METHOD2(ReceiveData, void(base::StringPiece data, bool flag_fin));
+};
+
+class HttpToSpdyConverterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ HttpToSpdyConverterTest() : converter_(GetParam(), &receiver_) {}
+
+ protected:
+ const char* status_header_name() const {
+ return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Status :
+ mod_spdy::spdy::kSpdy3Status);
+ }
+ const char* version_header_name() const {
+ return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Version :
+ mod_spdy::spdy::kSpdy3Version);
+ }
+
+ MockSpdyReceiver receiver_;
+ mod_spdy::HttpToSpdyConverter converter_;
+ net::SpdyHeaderBlock expected_headers_;
+};
+
+// Simple response with a small payload. We should get a SYN_REPLY and a DATA
+// frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithContentLength) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "14";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+ expected_headers_["x-whatever"] = "foobar";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 14\r\n"
+ "Content-Type: text/plain\r\n"
+ "X-Whatever:foobar\r\n"
+ "\r\n"
+ "Hello, world!\n"
+ "\r\n"));
+}
+
+// The data arrives in two chunks, but they're small, so we should consolidate
+// them into a single DATA frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithChunking) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "Keep-Alive: timeout=10, max=5\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "6\r\n"
+ "Hello,\r\n"
+ "8\r\n"
+ " world!\n\r\n"
+ "0\r\n"
+ "\r\n"));
+}
+
+// Test that we don't get tripped up if there is garbage after the end of
+// a chunked message.
+TEST_P(HttpToSpdyConverterTest, ChunkedEncodingWithTrailingGarbage) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "E\r\n"
+ "Hello, world!\n\r\n"
+ "0\r\n"
+ "0\r\n" // multiple last-chunks
+ "\r\n\x1bGaRbAgE")); // and also some garbage bytes
+}
+
+// No response body, so we should get the FLAG_FIN on the SYN_REPLY, and no
+// DATA frames.
+TEST_P(HttpToSpdyConverterTest, NoResponseBody) {
+ expected_headers_[status_header_name()] = "301";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_["location"] = "https://www.example.com/";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 301 Moved permenantly\r\n"
+ "Location: https://www.example.com/\r\n"
+ "\r\n"));
+}
+
+// Simple response with a large payload. We should get a SYN_REPLY and
+// multiple DATA frames.
+TEST_P(HttpToSpdyConverterTest, BreakUpLargeDataIntoMultipleFrames) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "10000";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(1808, 'x')), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n" +
+ std::string(10000, 'x') +
+ "\r\n"));
+}
+
+// Test that we buffer data until we get the full frame.
+TEST_P(HttpToSpdyConverterTest, BufferUntilWeHaveACompleteFrame) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "4096";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ // Send some of the headers. We shouldn't get anything out yet.
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 4096\r\n"));
+ // Send the rest of the headers, and some of the data. We should get the
+ // SYN_REPLY now, but no data yet.
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ ASSERT_TRUE(converter_.ProcessInput(
+ "Content-Type: text/plain\r\n"
+ "\r\n" +
+ std::string(2000, 'x')));
+ // Send some more data, but still not enough for a full frame.
+ ASSERT_TRUE(converter_.ProcessInput(std::string(2000, 'x')));
+ // Send the last of the data. We should finally get the one DATA frame.
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(true)));
+ ASSERT_TRUE(converter_.ProcessInput(std::string(96, 'x')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, RespectFlushes) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "4096";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ // Send the headers and some of the data (not enough for a full frame). We
+ // should get the headers out, but no data yet.
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 4096\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n" +
+ std::string(2000, 'x')));
+ // Perform a flush. We should get the data sent so far.
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2000, 'x')), Eq(false)));
+ converter_.Flush();
+ // Send the rest of the data. We should get out a second DATA frame, with
+ // FLAG_FIN set.
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2096, 'y')), Eq(true)));
+ ASSERT_TRUE(converter_.ProcessInput(std::string(2096, 'y')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, FlushAfterEndDoesNothing) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "6";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("foobar"), Eq(true)));
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 6\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "foobar"));
+ // Flushing after we're done (even multiple times) should be permitted, but
+ // should do nothing.
+ converter_.Flush();
+ converter_.Flush();
+ converter_.Flush();
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, HttpToSpdyConverterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/protocol_util.h"
+
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace http {
+
+extern const char* const kAcceptEncoding = "accept-encoding";
+extern const char* const kConnection = "connection";
+extern const char* const kContentLength = "content-length";
+extern const char* const kContentType = "content-type";
+extern const char* const kHost = "host";
+extern const char* const kKeepAlive = "keep-alive";
+extern const char* const kProxyConnection = "proxy-connection";
+extern const char* const kReferer = "referer";
+extern const char* const kTransferEncoding = "transfer-encoding";
+extern const char* const kXAssociatedContent = "x-associated-content";
+extern const char* const kXModSpdy = "x-mod-spdy";
+
+extern const char* const kChunked = "chunked";
+extern const char* const kGzipDeflate = "gzip,deflate";
+
+} // namespace http
+
+namespace spdy {
+
+extern const char* const kSpdy2Method = "method";
+extern const char* const kSpdy2Scheme = "scheme";
+extern const char* const kSpdy2Status = "status";
+extern const char* const kSpdy2Url = "url";
+extern const char* const kSpdy2Version = "version";
+
+extern const char* const kSpdy3Host = ":host";
+extern const char* const kSpdy3Method = ":method";
+extern const char* const kSpdy3Path = ":path";
+extern const char* const kSpdy3Scheme = ":scheme";
+extern const char* const kSpdy3Status = ":status";
+extern const char* const kSpdy3Version = ":version";
+
+} // namespace spdy
+
+net::SpdyMajorVersion SpdyVersionToFramerVersion(spdy::SpdyVersion version) {
+ switch (version) {
+ case spdy::SPDY_VERSION_2:
+ return net::SPDY2;
+ case spdy::SPDY_VERSION_3:
+ case spdy::SPDY_VERSION_3_1:
+ return net::SPDY3;
+ default:
+ LOG(DFATAL) << "Invalid SpdyVersion value: " << version;
+ return static_cast<net::SpdyMajorVersion>(0);
+ }
+}
+
+const char* SpdyVersionNumberString(spdy::SpdyVersion version) {
+ switch (version) {
+ case spdy::SPDY_VERSION_2: return "2";
+ case spdy::SPDY_VERSION_3: return "3";
+ case spdy::SPDY_VERSION_3_1: return "3.1";
+ default:
+ LOG(DFATAL) << "Invalid SpdyVersion value: " << version;
+ return "?";
+ }
+}
+
+const char* GoAwayStatusCodeToString(net::SpdyGoAwayStatus status) {
+ switch (status) {
+ case net::GOAWAY_OK: return "OK";
+ case net::GOAWAY_PROTOCOL_ERROR: return "PROTOCOL_ERROR";
+ case net::GOAWAY_INTERNAL_ERROR: return "INTERNAL_ERROR";
+ default: return "<unknown>";
+ }
+}
+
+const char* SettingsIdToString(net::SpdySettingsIds id) {
+ switch (id) {
+ case net::SETTINGS_UPLOAD_BANDWIDTH: return "UPLOAD_BANDWIDTH";
+ case net::SETTINGS_DOWNLOAD_BANDWIDTH: return "DOWNLOAD_BANDWIDTH";
+ case net::SETTINGS_ROUND_TRIP_TIME: return "ROUND_TRIP_TIME";
+ case net::SETTINGS_MAX_CONCURRENT_STREAMS: return "MAX_CONCURRENT_STREAMS";
+ case net::SETTINGS_CURRENT_CWND: return "CURRENT_CWND";
+ case net::SETTINGS_DOWNLOAD_RETRANS_RATE: return "DOWNLOAD_RETRANS_RATE";
+ case net::SETTINGS_INITIAL_WINDOW_SIZE: return "INITIAL_WINDOW_SIZE";
+ default: return "<unknown>";
+ }
+}
+
+bool IsInvalidSpdyResponseHeader(base::StringPiece key) {
+ // The following headers are forbidden in SPDY responses (SPDY draft 3
+ // section 3.2.2).
+ return (LowerCaseEqualsASCII(key.begin(), key.end(), http::kConnection) ||
+ LowerCaseEqualsASCII(key.begin(), key.end(), http::kKeepAlive) ||
+ LowerCaseEqualsASCII(key.begin(), key.end(),
+ http::kProxyConnection) ||
+ LowerCaseEqualsASCII(key.begin(), key.end(),
+ http::kTransferEncoding));
+}
+
+net::SpdyPriority LowestSpdyPriorityForVersion(
+ spdy::SpdyVersion spdy_version) {
+ return (spdy_version < spdy::SPDY_VERSION_3 ? 3u : 7u);
+}
+
+void MergeInHeader(base::StringPiece key, base::StringPiece value,
+ net::SpdyHeaderBlock* headers) {
+ // The SPDY spec requires that header names be lowercase, so forcibly
+ // lowercase the key here.
+ std::string lower_key(key.as_string());
+ StringToLowerASCII(&lower_key);
+
+ net::SpdyHeaderBlock::iterator iter = headers->find(lower_key);
+ if (iter == headers->end()) {
+ (*headers)[lower_key] = value.as_string();
+ } else {
+ iter->second.push_back('\0');
+ value.AppendToString(&iter->second);
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
+#define MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
+
+#include "base/strings/string_piece.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace http {
+
+// HTTP header names. These values are all lower-case, so they can be used
+// directly in SPDY header blocks.
+extern const char* const kAcceptEncoding;
+extern const char* const kConnection;
+extern const char* const kContentLength;
+extern const char* const kContentType;
+extern const char* const kHost;
+extern const char* const kKeepAlive;
+extern const char* const kProxyConnection;
+extern const char* const kReferer;
+extern const char* const kTransferEncoding;
+extern const char* const kXAssociatedContent;
+extern const char* const kXModSpdy;
+
+// HTTP header values.
+extern const char* const kChunked;
+extern const char* const kGzipDeflate;
+
+} // namespace http
+
+namespace spdy {
+
+// Represents a specific SPDY version, including experimental versions such as
+// SPDY/3.1 (which uses version 3 frames, but has extra semantics borrowed from
+// SPDY/4).
+enum SpdyVersion {
+ SPDY_VERSION_NONE, // not using SPDY
+ SPDY_VERSION_2, // SPDY/2
+ SPDY_VERSION_3, // SPDY/3
+ SPDY_VERSION_3_1 // SPDY/3.1 (SPDY/3 framing, but with new flow control)
+};
+
+// Magic header names for SPDY v2.
+extern const char* const kSpdy2Method;
+extern const char* const kSpdy2Scheme;
+extern const char* const kSpdy2Status;
+extern const char* const kSpdy2Url;
+extern const char* const kSpdy2Version;
+
+// Magic header names for SPDY v3.
+extern const char* const kSpdy3Host;
+extern const char* const kSpdy3Method;
+extern const char* const kSpdy3Path;
+extern const char* const kSpdy3Scheme;
+extern const char* const kSpdy3Status;
+extern const char* const kSpdy3Version;
+
+} // namespace spdy
+
+// Given a SpdyVersion enum value, return the framer version number to use.
+// The argument must not be SPDY_VERSION_NONE.
+net::SpdyMajorVersion SpdyVersionToFramerVersion(spdy::SpdyVersion version);
+
+// Given a SpdyVersion enum value (other than SPDY_VERSION_NONE), return a
+// string for the version number (e.g. "3" or "3.1").
+const char* SpdyVersionNumberString(spdy::SpdyVersion version);
+
+// Convert various SPDY enum types to strings.
+const char* GoAwayStatusCodeToString(net::SpdyGoAwayStatus status);
+inline const char* RstStreamStatusCodeToString(
+ net::SpdyRstStreamStatus status) {
+ return net::SpdyFramer::StatusCodeToString(status);
+}
+const char* SettingsIdToString(net::SpdySettingsIds id);
+
+// Return true if this header is forbidden in SPDY responses (ignoring case).
+bool IsInvalidSpdyResponseHeader(base::StringPiece key);
+
+// Return the SpdyPriority representing the least important priority for the
+// given SPDY version. For SPDY v2 and below, it's 3; for SPDY v3 and above,
+// it's 7. (The most important SpdyPriority is always 0.)
+net::SpdyPriority LowestSpdyPriorityForVersion(spdy::SpdyVersion spdy_version);
+
+// Add a header to a header table, lower-casing and merging if necessary.
+void MergeInHeader(base::StringPiece key, base::StringPiece value,
+ net::SpdyHeaderBlock* headers);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
--- /dev/null
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/protocol_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(ProtocolUtilTest, InvalidSpdyResponseHeaders) {
+ // Forbidden headers should be rejected regardless of capitalization.
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("connection"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("Connection"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("cOnNeCtIoN"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("transfer-encoding"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("Transfer-Encoding"));
+}
+
+TEST(ProtocolUtilTest, ValidSpdyResponseHeaders) {
+ // Permitted headers should be accepted regardless of capitalization (SPDY
+ // requires header names to be lowercase, but this function shouldn't be
+ // checking that).
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader("content-length"));
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader("Content-Length"));
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader(
+ "x-header-we-have-never-heard-of"));
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader(
+ "X-HEADER-WE-HAVE-NEVER-HEARD-OF"));
+}
+
+TEST(ProtocolUtilTest, MergeIntoEmpty) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("content-length", "256", &headers);
+ ASSERT_EQ(1u, headers.size());
+ ASSERT_EQ("256", headers["content-length"]);
+}
+
+TEST(ProtocolUtilTest, MakeLowerCase) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("Content-Length", "256", &headers);
+ ASSERT_EQ(1u, headers.size());
+ ASSERT_EQ(0u, headers.count("Content-Length"));
+ ASSERT_EQ("256", headers["content-length"]);
+}
+
+TEST(ProtocolUtilTest, MergeDifferentHeaders) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("x-foo", "bar", &headers);
+ ASSERT_EQ(1u, headers.size());
+ ASSERT_EQ("bar", headers["x-foo"]);
+
+ mod_spdy::MergeInHeader("x-baz", "quux", &headers);
+ ASSERT_EQ(2u, headers.size());
+ ASSERT_EQ("bar", headers["x-foo"]);
+ ASSERT_EQ("quux", headers["x-baz"]);
+}
+
+TEST(ProtocolUtilTest, MergeRepeatedHeader) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("x-foo", "bar", &headers);
+ ASSERT_EQ(1u, headers.size());
+ const std::string expected1("bar");
+ ASSERT_EQ(expected1, headers["x-foo"]);
+
+ mod_spdy::MergeInHeader("x-foo", "baz", &headers);
+ ASSERT_EQ(1u, headers.size());
+ const std::string expected2("bar\0baz", 7);
+ ASSERT_EQ(expected2, headers["x-foo"]);
+
+ mod_spdy::MergeInHeader("x-foo", "quux", &headers);
+ ASSERT_EQ(1u, headers.size());
+ const std::string expected3("bar\0baz\0quux", 12);
+ ASSERT_EQ(expected3, headers["x-foo"]);
+}
+
+} // namespace
--- /dev/null
+// Copyright 2013 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.
+
+#include "mod_spdy/common/server_push_discovery_learner.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/strings/string_util.h"
+
+namespace mod_spdy {
+
+namespace {
+
+int32_t GetPriorityFromExtension(const std::string& url) {
+ if (EndsWith(url, ".js", false)) {
+ return 1;
+ } else if (EndsWith(url, ".css", false)) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+} // namespace
+
+ServerPushDiscoveryLearner::ServerPushDiscoveryLearner() {}
+
+std::vector<ServerPushDiscoveryLearner::Push>
+ServerPushDiscoveryLearner::GetPushes(const std::string& master_url) {
+ base::AutoLock lock(lock_);
+ UrlData& url_data = url_data_[master_url];
+ std::vector<Push> pushes;
+
+ uint64_t threshold = url_data.first_hit_count / 2;
+
+ std::vector<AdjacentData> significant_adjacents;
+
+ for (std::map<std::string, AdjacentData>::const_iterator it =
+ url_data.adjacents.begin(); it != url_data.adjacents.end(); ++it) {
+ if (it->second.hit_count >= threshold)
+ significant_adjacents.push_back(it->second);
+ }
+
+ // Sort by average time from initial request. We want to provide the child
+ // resources that the client needs immediately with a higher priority.
+ std::sort(significant_adjacents.begin(), significant_adjacents.end(),
+ &CompareAdjacentDataByAverageTimeFromInit);
+
+ for (size_t i = 0; i < significant_adjacents.size(); ++i) {
+ const AdjacentData& adjacent = significant_adjacents[i];
+
+ // Give certain URLs fixed high priorities based on their extension.
+ int32_t priority = GetPriorityFromExtension(adjacent.adjacent_url);
+
+ // Otherwise, assign a higher priority based on its average request order.
+ if (priority < 0) {
+ priority = 2 + (i * 6 / significant_adjacents.size());
+ }
+
+ pushes.push_back(Push(adjacent.adjacent_url, priority));
+ }
+
+ return pushes;
+}
+
+void ServerPushDiscoveryLearner::AddFirstHit(const std::string& master_url) {
+ base::AutoLock lock(lock_);
+ UrlData& url_data = url_data_[master_url];
+ ++url_data.first_hit_count;
+}
+
+void ServerPushDiscoveryLearner::AddAdjacentHit(const std::string& master_url,
+ const std::string& adjacent_url,
+ int64_t time_from_init) {
+ base::AutoLock lock(lock_);
+ std::map<std::string, AdjacentData>& master_url_adjacents =
+ url_data_[master_url].adjacents;
+
+ if (master_url_adjacents.find(adjacent_url) == master_url_adjacents.end()) {
+ master_url_adjacents.insert(
+ make_pair(adjacent_url, AdjacentData(adjacent_url)));
+ }
+
+ AdjacentData& adjacent_data = master_url_adjacents.find(adjacent_url)->second;
+
+ ++adjacent_data.hit_count;
+ double inverse_hit_count = 1.0 / adjacent_data.hit_count;
+
+ adjacent_data.average_time_from_init =
+ inverse_hit_count * time_from_init +
+ (1 - inverse_hit_count) * adjacent_data.average_time_from_init;
+}
+
+// static
+bool ServerPushDiscoveryLearner::CompareAdjacentDataByAverageTimeFromInit(
+ const AdjacentData& a, const AdjacentData& b) {
+ return a.average_time_from_init < b.average_time_from_init;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2013 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.
+
+#ifndef MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_
+#define MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+// Used to keep track of request patterns and generate X-Associated-Content.
+// Stores the initial |master_url| request and the subsequent |adjacent_url|s.
+// Generates reasonable pushes based on a simple heuristic.
+class ServerPushDiscoveryLearner {
+ public:
+ struct Push {
+ Push(const std::string& adjacent_url, net::SpdyPriority priority)
+ : adjacent_url(adjacent_url),
+ priority(priority) {
+ }
+
+ std::string adjacent_url;
+ net::SpdyPriority priority;
+ };
+
+ ServerPushDiscoveryLearner();
+
+ // Gets a list of child resource pushes for a given |master_url|.
+ std::vector<Push> GetPushes(const std::string& master_url);
+
+ // Called when module receives an initial master request for a page, and
+ // module intends to log future child resource requests for learning.
+ void AddFirstHit(const std::string& master_url);
+
+ // Called when module receives child resource requests associated with
+ // a master request received earlier. |time_from_init| is the time in
+ // microseconds between this adjacent hit on |adjacent_url| and the initial
+ // hit on the |master_url|.
+ void AddAdjacentHit(const std::string& master_url,
+ const std::string& adjacent_url, int64_t time_from_init);
+
+ private:
+ struct AdjacentData {
+ AdjacentData(const std::string& adjacent_url)
+ : adjacent_url(adjacent_url),
+ hit_count(0),
+ average_time_from_init(0) {
+ }
+
+ std::string adjacent_url;
+ uint64_t hit_count;
+ int64_t average_time_from_init;
+ };
+
+ struct UrlData {
+ UrlData() : first_hit_count(0) {}
+
+ uint64_t first_hit_count;
+ std::map<std::string, AdjacentData> adjacents;
+ };
+
+ static bool CompareAdjacentDataByAverageTimeFromInit(const AdjacentData& a,
+ const AdjacentData& b);
+
+ std::map<std::string, UrlData> url_data_;
+ base::Lock lock_;
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_
--- /dev/null
+// Copyright 2013 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.
+
+#include "mod_spdy/common/server_push_discovery_learner.h"
+
+#include "gtest/gtest.h"
+
+namespace mod_spdy {
+
+TEST(ServerPushDiscoveryLearnerTest, TrivialNoPush) {
+ ServerPushDiscoveryLearner learner;
+
+ EXPECT_TRUE(learner.GetPushes("a").empty());
+
+ learner.AddFirstHit("a");
+ learner.AddFirstHit("a");
+ learner.AddFirstHit("a");
+ learner.AddFirstHit("a");
+
+ EXPECT_TRUE(learner.GetPushes("a").empty());
+
+ // Add an adjacent hit, but it should not be enough to generate a push.
+ learner.AddAdjacentHit("a", "b", 0);
+
+ EXPECT_TRUE(learner.GetPushes("a").empty());
+}
+
+TEST(ServerPushDiscoveryLearnerTest, TrivialYesPush) {
+ ServerPushDiscoveryLearner learner;
+
+ learner.AddFirstHit("a");
+ learner.AddAdjacentHit("a", "b", 0);
+
+ std::vector<ServerPushDiscoveryLearner::Push> pushes = learner.GetPushes("a");
+ EXPECT_FALSE(pushes.empty());
+ EXPECT_EQ("b", pushes.front().adjacent_url);
+}
+
+TEST(ServerPushDiscoveryLearnerTest, PushOrder) {
+ ServerPushDiscoveryLearner learner;
+
+ learner.AddFirstHit("a");
+ learner.AddAdjacentHit("a", "b", 1);
+ learner.AddAdjacentHit("a", "c", 2);
+
+ learner.AddFirstHit("a");
+ learner.AddAdjacentHit("a", "b", 2);
+ learner.AddAdjacentHit("a", "c", 3);
+
+ learner.AddFirstHit("a");
+ learner.AddAdjacentHit("a", "b", 3);
+ learner.AddAdjacentHit("a", "c", 4);
+
+ std::vector<ServerPushDiscoveryLearner::Push> pushes = learner.GetPushes("a");
+ EXPECT_EQ(2u, pushes.size());
+ EXPECT_EQ("b", pushes.front().adjacent_url);
+ EXPECT_EQ("c", pushes.back().adjacent_url);
+}
+
+TEST(ServerPushDiscoveryLearnerTest, TurnoverPoint) {
+ ServerPushDiscoveryLearner learner;
+
+ uint64_t a_requests = 0;
+ uint64_t b_requests = 0;
+
+ // Put in 20 initial requests with no child requests.
+ for (int i = 0; i < 20; ++i) {
+ learner.AddFirstHit("a");
+ ++a_requests;
+ }
+
+ // Put in more b requests until it tips over
+ for (int i = 0; i < 50; ++i) {
+ learner.AddAdjacentHit("a", "b", 0);
+ ++b_requests;
+ std::vector<ServerPushDiscoveryLearner::Push> pushes =
+ learner.GetPushes("a");
+
+ if (b_requests >= (a_requests / 2)) {
+ ASSERT_TRUE(pushes.size() == 1) << "(a, b) = " << a_requests << ","
+ << b_requests;
+ EXPECT_EQ("b", pushes.front().adjacent_url);
+ } else {
+ EXPECT_TRUE(pushes.empty()) << "(a, b) = " << a_requests << ","
+ << b_requests;
+ }
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2013 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.
+
+#include "mod_spdy/common/server_push_discovery_session.h"
+
+namespace mod_spdy {
+
+const int64_t kServerPushSessionTimeout = 1000000; // 1 second in microseconds.
+
+ServerPushDiscoverySessionPool::ServerPushDiscoverySessionPool()
+ : next_session_id_(0) {
+}
+
+ServerPushDiscoverySession* ServerPushDiscoverySessionPool::GetExistingSession(
+ SessionId session_id,
+ int64_t request_time) {
+ base::AutoLock lock(lock_);
+ std::map<SessionId, ServerPushDiscoverySession>::iterator it =
+ session_cache_.find(session_id);
+ if (it == session_cache_.end() ||
+ it->second.TimeFromInit(request_time) > kServerPushSessionTimeout) {
+ return NULL;
+ }
+
+ return &(it->second);
+}
+
+ServerPushDiscoverySessionPool::SessionId
+ServerPushDiscoverySessionPool::CreateSession(
+ int64_t request_time,
+ const std::string& request_url,
+ bool took_push) {
+ base::AutoLock lock(lock_);
+ CleanExpired(request_time);
+ // Create a session to track this request chain
+ SessionId session_id = ++next_session_id_;
+ session_cache_.insert(
+ std::make_pair(session_id,
+ ServerPushDiscoverySession(
+ session_id, request_time, request_url, took_push)));
+ return session_id;
+}
+
+void ServerPushDiscoverySessionPool::CleanExpired(int64_t request_time) {
+ lock_.AssertAcquired();
+
+ std::map<int64_t, ServerPushDiscoverySession>::iterator it =
+ session_cache_.begin();
+ while (it != session_cache_.end()) {
+ if (it->second.TimeFromInit(request_time) > kServerPushSessionTimeout) {
+ session_cache_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+ServerPushDiscoverySession::ServerPushDiscoverySession(
+ ServerPushDiscoverySessionPool::SessionId session_id,
+ int64_t initial_request_time,
+ const std::string& master_url,
+ bool took_push)
+ : session_id_(session_id),
+ initial_request_time_(initial_request_time),
+ master_url_(master_url),
+ took_push_(took_push),
+ last_access_(initial_request_time) {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2013 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.
+
+#ifndef MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_SESSION_H_
+#define MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_SESSION_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+
+namespace mod_spdy {
+
+class ServerPushDiscoverySession;
+
+extern const int64_t kServerPushSessionTimeout;
+
+// This class manages a pool of sessions used to discover X-Associated-Content.
+// It tracks an initial request, i.e., for 'index.html', and all its child
+// requests, i.e., 'logo.gif', 'style.css'. This should be created during
+// per-process initialization. Class may be called from multiple threads.
+class ServerPushDiscoverySessionPool {
+ public:
+ typedef int64_t SessionId;
+
+ ServerPushDiscoverySessionPool();
+
+ // Retrieves an existing session. Returns NULL if it's been timed out already.
+ // In which case the module should create a new session. The returned pointer
+ // is an object owned by the pool and must not be deleted by the caller.
+ ServerPushDiscoverySession* GetExistingSession(SessionId session_id,
+ int64_t request_time);
+
+ // Creates a new session. |took_push| denotes if the the initial request
+ // response included an auto-learned X-Associated-Content header, since we
+ // don't want to self-reinforce our URL statistics. Returns the session_id
+ // for user agent storage.
+ int64_t CreateSession(int64_t request_time,
+ const std::string& request_url,
+ bool took_push);
+
+ private:
+ // Caller should be holding |lock_|.
+ void CleanExpired(int64_t request_time);
+
+ SessionId next_session_id_;
+ std::map<SessionId, ServerPushDiscoverySession> session_cache_;
+
+ base::Lock lock_;
+};
+
+// Represents an initial page request and all its child resource requests.
+class ServerPushDiscoverySession {
+ public:
+ // Update the last access time on this session, extending its lifetime.
+ void UpdateLastAccessTime(int64_t now) { last_access_ = now; }
+
+ // Returns the elapsed microseconds between the initial request and this one.
+ int64_t TimeFromInit(int64_t request_time) const {
+ return request_time - initial_request_time_;
+ }
+
+ const std::string& master_url() const { return master_url_; }
+ bool took_push() const { return took_push_; }
+
+ private:
+ friend class ServerPushDiscoverySessionPool;
+
+ ServerPushDiscoverySession(
+ ServerPushDiscoverySessionPool::SessionId session_id,
+ int64_t initial_request_time,
+ const std::string& master_url,
+ bool took_push);
+
+ ServerPushDiscoverySessionPool::SessionId session_id_;
+ int64_t initial_request_time_;
+ std::string master_url_;
+ int64_t took_push_;
+
+ int64_t last_access_;
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_SESSION_H_
--- /dev/null
+// Copyright 2013 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.
+
+#include "mod_spdy/common/server_push_discovery_session.h"
+
+#include "gtest/gtest.h"
+
+namespace mod_spdy {
+
+TEST(ServerPushDiscoverySessionTest, NoSession) {
+ ServerPushDiscoverySessionPool pool;
+ EXPECT_EQ(NULL, pool.GetExistingSession(0, 0));
+}
+
+TEST(ServerPushDiscoverySessionTest, GetSession) {
+ ServerPushDiscoverySessionPool pool;
+ std::vector<int64_t> session_ids;
+ for (int i = 0; i < 40; i++)
+ session_ids.push_back(pool.CreateSession(0, "", false));
+
+ for (int i = 0; i < 40; i++)
+ EXPECT_TRUE(pool.GetExistingSession(session_ids[i], 0));
+}
+
+TEST(ServerPushDiscoverySessionTest, ExpiryTest) {
+ ServerPushDiscoverySessionPool pool;
+ std::vector<int64_t> session_ids;
+ for (int i = 0; i < 20; i++) {
+ session_ids.push_back(pool.CreateSession(0, "", false));
+ }
+
+ for (int i = 0; i < 20; i++) {
+ int64_t time = i * kServerPushSessionTimeout / 10;
+ bool expired = time > kServerPushSessionTimeout;
+ session_ids.push_back(pool.CreateSession(0, "", false));
+
+ if (expired) {
+ EXPECT_FALSE(pool.GetExistingSession(session_ids[i], time));
+ } else {
+ EXPECT_TRUE(pool.GetExistingSession(session_ids[i], time));
+ }
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2013 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.
+
+#include "mod_spdy/common/shared_flow_control_window.h"
+
+#include "base/logging.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SharedFlowControlWindow::SharedFlowControlWindow(
+ int32 initial_input_window_size, int32 initial_output_window_size)
+ : condvar_(&lock_),
+ aborted_(false),
+ init_input_window_size_(initial_input_window_size),
+ input_window_size_(initial_input_window_size),
+ input_bytes_consumed_(0),
+ output_window_size_(initial_output_window_size) {}
+
+SharedFlowControlWindow::~SharedFlowControlWindow() {}
+
+void SharedFlowControlWindow::Abort() {
+ base::AutoLock autolock(lock_);
+ aborted_ = true;
+ condvar_.Broadcast();
+}
+
+bool SharedFlowControlWindow::is_aborted() const {
+ base::AutoLock autolock(lock_);
+ return aborted_;
+}
+
+int32 SharedFlowControlWindow::current_input_window_size() const {
+ base::AutoLock autolock(lock_);
+ return input_window_size_;
+}
+
+int32 SharedFlowControlWindow::current_output_window_size() const {
+ base::AutoLock autolock(lock_);
+ return output_window_size_;
+}
+
+int32 SharedFlowControlWindow::input_bytes_consumed() const {
+ base::AutoLock autolock(lock_);
+ return input_bytes_consumed_;
+}
+
+bool SharedFlowControlWindow::OnReceiveInputData(size_t length) {
+ base::AutoLock autolock(lock_);
+ if (aborted_) {
+ return true;
+ }
+ DCHECK_GE(input_window_size_, 0);
+ if (static_cast<size_t>(input_window_size_) < length) {
+ return false;
+ }
+ input_window_size_ -= length;
+ return true;
+}
+
+int32 SharedFlowControlWindow::OnInputDataConsumed(size_t length) {
+ base::AutoLock autolock(lock_);
+ if (aborted_) {
+ return 0;
+ }
+
+ DCHECK_GE(input_bytes_consumed_, 0);
+
+ // Check for overflow; this should never happen unless there is a bug in
+ // mod_spdy, since we should never say we've consumed more data than we've
+ // actually received.
+ {
+ const int64 new_input_bytes_consumed =
+ static_cast<int64>(input_bytes_consumed_) + static_cast<int64>(length);
+ CHECK_LE(new_input_bytes_consumed,
+ static_cast<int64>(init_input_window_size_));
+ CHECK_LE(new_input_bytes_consumed + static_cast<int64>(input_window_size_),
+ static_cast<int64>(init_input_window_size_));
+ input_bytes_consumed_ = new_input_bytes_consumed;
+ }
+
+ // Only send a WINDOW_UPDATE when we've consumed 1/16 of the maximum shared
+ // window size, so that we don't send lots of small WINDOW_UDPATE frames.
+ if (input_bytes_consumed_ < init_input_window_size_ / 16) {
+ return 0;
+ } else {
+ input_window_size_ += input_bytes_consumed_;
+ const int32 consumed = input_bytes_consumed_;
+ input_bytes_consumed_ = 0;
+ return consumed;
+ }
+}
+
+void SharedFlowControlWindow::OnInputDataConsumedSendUpdateIfNeeded(
+ size_t length, SpdyFramePriorityQueue* output_queue) {
+ const int32 update = OnInputDataConsumed(length);
+ if (update > 0) {
+ output_queue->Insert(SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyWindowUpdateIR(0, update));
+ }
+}
+
+int32 SharedFlowControlWindow::RequestOutputQuota(int32 amount_requested) {
+ base::AutoLock autolock(lock_);
+ DCHECK_GT(amount_requested, 0);
+
+ while (!aborted_ && output_window_size_ <= 0) {
+ condvar_.Wait();
+ }
+
+ if (aborted_) {
+ return 0;
+ }
+
+ // Give as much output quota as we can, but not more than is asked for.
+ DCHECK_GT(output_window_size_, 0);
+ const int32 amount_to_give = std::min(amount_requested, output_window_size_);
+ output_window_size_ -= amount_to_give;
+ DCHECK_GE(output_window_size_, 0);
+ return amount_to_give;
+}
+
+bool SharedFlowControlWindow::IncreaseOutputWindowSize(int32 delta) {
+ base::AutoLock autolock(lock_);
+ DCHECK_GE(delta, 0);
+ if (aborted_) {
+ return true;
+ }
+
+ // Check for overflow; this can happen if the client is misbehaving.
+ const int64 new_size =
+ static_cast<int64>(output_window_size_) + static_cast<int64>(delta);
+ if (new_size > static_cast<int64>(net::kSpdyMaximumWindowSize)) {
+ return false;
+ }
+
+ // Increase the shared output window size, and wake up any stream threads
+ // that are waiting for output quota.
+ output_window_size_ += delta;
+ DCHECK_LE(output_window_size_, net::kSpdyMaximumWindowSize);
+ if (output_window_size_ > 0) {
+ condvar_.Broadcast();
+ }
+ return true;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2013 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.
+
+#ifndef MOD_SPDY_COMMON_SHARED_FLOW_CONTROL_WINDOW_H_
+#define MOD_SPDY_COMMON_SHARED_FLOW_CONTROL_WINDOW_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace mod_spdy {
+
+class SpdyFramePriorityQueue;
+
+// SPDY/3.1 introduces an additional session-wide flow control window shared by
+// all streams, and represented in WINDOW_UPDATE frames as "stream 0". The
+// SharedFlowControlWindow class is a thread-safe object for tracking the size
+// of this shared flow control window and enforcing flow-control rules, in both
+// the input and output directions.
+class SharedFlowControlWindow {
+ public:
+ SharedFlowControlWindow(int32 initial_input_window_size,
+ int32 initial_output_window_size);
+ ~SharedFlowControlWindow();
+
+ // Wake up all threads blocked on other methods. Future method calls to this
+ // class will return immediately with no effect.
+ void Abort();
+
+ // Return true if Abort() has been called.
+ bool is_aborted() const;
+
+ // Get the current input/ouput window sizes (of course, it might very soon
+ // change if other threads are accessing this object). This is primarily
+ // useful for testing/debugging.
+ int32 current_input_window_size() const;
+ int32 current_output_window_size() const;
+ // How many input bytes have been consumed that _haven't_ yet been
+ // acknowledged by a WINDOW_UPDATE (signaled by OnInputDataConsumed)? This
+ // is primarily useful for testing/debugging.
+ int32 input_bytes_consumed() const;
+
+ // Called by the connection thread when input data is received from the
+ // client. Returns true (and reduces the input window size) on success, or
+ // false if the input window is too small to accept that much data, in which
+ // case the client has committed a flow control error and should be sent a
+ // GOAWAY. If the SharedFlowControlWindow has already been aborted
+ // (i.e. because the session is shutting down), returns true with no effect.
+ bool OnReceiveInputData(size_t length) WARN_UNUSED_RESULT;
+
+ // Called by stream threads when input data from the client has been
+ // consumed. If a session WINDOW_UPDATE (stream 0) should be sent, returns
+ // the size of the update to send; otherwise, returns zero. If the
+ // SharedFlowControlWindow has already been aborted, returns 0 with no
+ // effect.
+ int32 OnInputDataConsumed(size_t length) WARN_UNUSED_RESULT;
+
+ // Like OnInputDataConsumed, but automatically send a WINDOW_UPDATE for
+ // stream 0 if needed.
+ void OnInputDataConsumedSendUpdateIfNeeded(
+ size_t length, SpdyFramePriorityQueue* output_queue);
+
+ // This should be called by stream threads to consume quota from the shared
+ // flow control window. Consumes up to `amount_requested` bytes from the
+ // window (less if the window is currently smaller than `amount_requested`)
+ // and returns the number of bytes successfully consumed. If the window is
+ // currently empty, blocks until some value can be returned (or the
+ // SharedFlowControlWindow is aborted); if the SharedFlowControlWindow is
+ // aborted, returns zero. The `amount_requested` must be strictly positive.
+ int32 RequestOutputQuota(int32 amount_requested) WARN_UNUSED_RESULT;
+
+ // This should be called by the connection thread to adjust the window size,
+ // due to receiving a WINDOW_UPDATE frame from the client. The delta
+ // argument must be non-negative (WINDOW_UPDATE is never negative). Return
+ // false if the delta would cause the window size to overflow (in which case
+ // the client has committed a flow control error and should be sent a
+ // GOAWAY), true otherwise. If the SharedFlowControlWindow has already been
+ // aborted, returns true with no effect.
+ bool IncreaseOutputWindowSize(int32 delta) WARN_UNUSED_RESULT;
+
+ private:
+ mutable base::Lock lock_; // protects the below fields
+ base::ConditionVariable condvar_;
+ bool aborted_;
+ const int32 init_input_window_size_;
+ int32 input_window_size_;
+ int32 input_bytes_consumed_;
+ int32 output_window_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedFlowControlWindow);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SHARED_FLOW_CONTROL_WINDOW_H_
--- /dev/null
+// Copyright 2013 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.
+
+#include "mod_spdy/common/shared_flow_control_window.h"
+
+#include "base/threading/platform_thread.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Test that when we receive input data, the input window size decreases, and
+// if we try to receive more data than the window allows, then
+// OnReceiveInputData returns false.
+TEST(SharedFlowControlWindowTest, ReceiveInput) {
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+ ASSERT_EQ(1000, shared_window.current_input_window_size());
+
+ EXPECT_TRUE(shared_window.OnReceiveInputData(320));
+ ASSERT_EQ(680, shared_window.current_input_window_size());
+
+ EXPECT_TRUE(shared_window.OnReceiveInputData(600));
+ ASSERT_EQ(80, shared_window.current_input_window_size());
+
+ EXPECT_FALSE(shared_window.OnReceiveInputData(100));
+ ASSERT_EQ(80, shared_window.current_input_window_size());
+
+ EXPECT_TRUE(shared_window.OnReceiveInputData(80));
+ ASSERT_EQ(0, shared_window.current_input_window_size());
+
+ EXPECT_FALSE(shared_window.OnReceiveInputData(1));
+ ASSERT_EQ(0, shared_window.current_input_window_size());
+}
+
+// Test that when we consume input data that we've already received, the input
+// window size goes up, but only once we've consumed enough total data for it
+// to be worth it to send a WINDOW_UPDATE.
+TEST(SharedFlowControlWindowTest, ConsumeInput) {
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+ ASSERT_FALSE(shared_window.is_aborted());
+ ASSERT_EQ(1000, shared_window.current_input_window_size());
+
+ EXPECT_TRUE(shared_window.OnReceiveInputData(1000));
+ EXPECT_EQ(0, shared_window.current_input_window_size());
+
+ EXPECT_EQ(0, shared_window.OnInputDataConsumed(10));
+ EXPECT_EQ(0, shared_window.current_input_window_size());
+ EXPECT_EQ(10, shared_window.input_bytes_consumed());
+
+ EXPECT_EQ(0, shared_window.OnInputDataConsumed(40));
+ EXPECT_EQ(0, shared_window.current_input_window_size());
+ EXPECT_EQ(50, shared_window.input_bytes_consumed());
+
+ EXPECT_EQ(550, shared_window.OnInputDataConsumed(500));
+ EXPECT_EQ(550, shared_window.current_input_window_size());
+ EXPECT_EQ(0, shared_window.input_bytes_consumed());
+
+ EXPECT_EQ(0, shared_window.OnInputDataConsumed(10));
+ EXPECT_EQ(550, shared_window.current_input_window_size());
+ EXPECT_EQ(10, shared_window.input_bytes_consumed());
+
+ EXPECT_EQ(450, shared_window.OnInputDataConsumed(440));
+ EXPECT_EQ(1000, shared_window.current_input_window_size());
+ EXPECT_EQ(0, shared_window.input_bytes_consumed());
+
+ shared_window.Abort();
+ ASSERT_TRUE(shared_window.is_aborted());
+
+ EXPECT_TRUE(shared_window.OnReceiveInputData(1));
+ EXPECT_EQ(0, shared_window.OnInputDataConsumed(1000));
+}
+
+// Test that OnInputDataConsumedSendUpdateIfNeeded sends WINDOW_UPDATE frames
+// correctly.
+TEST(SharedFlowControlWindowTest, ConsumeInputSendUpdate) {
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+ ASSERT_EQ(1000, shared_window.current_input_window_size());
+
+ EXPECT_TRUE(shared_window.OnReceiveInputData(1000));
+ EXPECT_EQ(0, shared_window.current_input_window_size());
+
+ mod_spdy::SpdyFramePriorityQueue queue;
+ net::SpdyFrameIR* raw_frame;
+
+ shared_window.OnInputDataConsumedSendUpdateIfNeeded(5, &queue);
+ EXPECT_EQ(0, shared_window.current_input_window_size());
+ EXPECT_EQ(5, shared_window.input_bytes_consumed());
+ ASSERT_FALSE(queue.Pop(&raw_frame));
+
+ shared_window.OnInputDataConsumedSendUpdateIfNeeded(933, &queue);
+ EXPECT_EQ(938, shared_window.current_input_window_size());
+ EXPECT_EQ(0, shared_window.input_bytes_consumed());
+ ASSERT_TRUE(queue.Pop(&raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, mod_spdy::testing::IsWindowUpdate(0, 938));
+ ASSERT_FALSE(queue.Pop(&raw_frame));
+}
+
+// Test basic usage of RequestOutputQuota and IncreaseOutputWindowSize.
+TEST(SharedFlowControlWindowTest, OutputBasic) {
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+ EXPECT_FALSE(shared_window.is_aborted());
+ EXPECT_EQ(1000, shared_window.current_output_window_size());
+
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(0));
+ EXPECT_EQ(1000, shared_window.current_output_window_size());
+
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(47));
+ EXPECT_EQ(1047, shared_window.current_output_window_size());
+
+ EXPECT_EQ(800, shared_window.RequestOutputQuota(800));
+ EXPECT_EQ(247, shared_window.current_output_window_size());
+
+ EXPECT_EQ(247, shared_window.RequestOutputQuota(800));
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(2000));
+ EXPECT_EQ(2000, shared_window.current_output_window_size());
+
+ // After aborting, RequestOutputQuota always returns zero (without blocking).
+ EXPECT_FALSE(shared_window.is_aborted());
+ shared_window.Abort();
+ EXPECT_TRUE(shared_window.is_aborted());
+ EXPECT_EQ(0, shared_window.RequestOutputQuota(800));
+ EXPECT_EQ(0, shared_window.RequestOutputQuota(9999));
+}
+
+// When run, a RequestOutputQuotaTask requests quota from the given
+// SharedFlowControlWindow.
+class RequestOutputQuotaTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+ RequestOutputQuotaTask(mod_spdy::SharedFlowControlWindow* window,
+ int32 request)
+ : window_(window), request_(request), received_(-1) {}
+ virtual void Run() {
+ received_ = window_->RequestOutputQuota(request_);
+ }
+ int32 received() const { return received_; }
+ private:
+ mod_spdy::SharedFlowControlWindow* const window_;
+ const int32 request_;
+ int32 received_;
+ DISALLOW_COPY_AND_ASSIGN(RequestOutputQuotaTask);
+};
+
+// Test that RequestOutputQuota blocks if the window is completely empty.
+TEST(SharedFlowControlWindowTest, OutputBlocking) {
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 350);
+
+ EXPECT_EQ(200, shared_window.RequestOutputQuota(200));
+ EXPECT_EQ(150, shared_window.current_output_window_size());
+
+ EXPECT_EQ(150, shared_window.RequestOutputQuota(200));
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ // Start an async task to request 200 bytes. It should block, because the
+ // window is empty.
+ RequestOutputQuotaTask* task =
+ new RequestOutputQuotaTask(&shared_window, 200);
+ mod_spdy::testing::AsyncTaskRunner runner(task);
+ ASSERT_TRUE(runner.Start());
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+ runner.notification()->ExpectNotSet();
+
+ // Now increase the window size. RequestOutputQuota should unblock and return
+ // what's available.
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(63));
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(63, task->received());
+}
+
+// Test that RequestOutputQuota unblocks if we abort.
+TEST(SharedFlowControlWindowTest, OutputAborting) {
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 350);
+
+ EXPECT_EQ(350, shared_window.RequestOutputQuota(500));
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ // Start an async task to request 200 bytes. It should block, because the
+ // window is empty.
+ RequestOutputQuotaTask* task =
+ new RequestOutputQuotaTask(&shared_window, 200);
+ mod_spdy::testing::AsyncTaskRunner runner(task);
+ ASSERT_TRUE(runner.Start());
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+ runner.notification()->ExpectNotSet();
+
+ // Now abort. RequestOutputQuota should unblock and return zero.
+ EXPECT_FALSE(shared_window.is_aborted());
+ shared_window.Abort();
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(0, task->received());
+ EXPECT_TRUE(shared_window.is_aborted());
+
+ // Abort again, to check that it's idempotent.
+ shared_window.Abort();
+ EXPECT_TRUE(shared_window.is_aborted());
+ EXPECT_EQ(0, shared_window.RequestOutputQuota(800));
+}
+
+} // namespace
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+
+#include <list>
+#include <map>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SpdyFramePriorityQueue::SpdyFramePriorityQueue()
+ : condvar_(&lock_) {}
+
+SpdyFramePriorityQueue::~SpdyFramePriorityQueue() {
+ for (QueueMap::iterator iter = queue_map_.begin();
+ iter != queue_map_.end(); ++iter) {
+ FrameList* list = iter->second;
+ STLDeleteContainerPointers(list->begin(), list->end());
+ delete list;
+ }
+}
+
+bool SpdyFramePriorityQueue::IsEmpty() const {
+ base::AutoLock autolock(lock_);
+ return queue_map_.empty();
+}
+
+const int SpdyFramePriorityQueue::kTopPriority = -1;
+
+void SpdyFramePriorityQueue::Insert(int priority, net::SpdyFrameIR* frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ // Get the frame list for the given priority; if it doesn't currently exist,
+ // create it in the map.
+ FrameList* list = NULL;
+ QueueMap::iterator iter = queue_map_.find(priority);
+ if (iter == queue_map_.end()) {
+ list = new FrameList;
+ queue_map_[priority] = list;
+ } else {
+ list = iter->second;
+ }
+ DCHECK(list);
+
+ // Add the frame to the end of the list, and wake up at most one thread
+ // sleeping on a BlockingPop.
+ list->push_back(frame);
+ condvar_.Signal();
+}
+
+bool SpdyFramePriorityQueue::Pop(net::SpdyFrameIR** frame) {
+ base::AutoLock autolock(lock_);
+ return InternalPop(frame);
+}
+
+bool SpdyFramePriorityQueue::BlockingPop(const base::TimeDelta& max_time,
+ net::SpdyFrameIR** frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ const base::TimeDelta zero = base::TimeDelta();
+ base::TimeDelta time_remaining = max_time;
+ while (time_remaining > zero && queue_map_.empty()) {
+ // TODO(mdsteele): It appears from looking at the Chromium source code that
+ // HighResNow() is "expensive" on Windows (how expensive, I am not sure);
+ // however, the other options for getting a "now" time either don't
+ // guarantee monotonicity (so time might go backwards) or might be too
+ // low-resolution for our purposes, so I think we'd better stick with this
+ // for now. But is there a better way to do what we're doing here?
+ const base::TimeTicks start = base::TimeTicks::HighResNow();
+ condvar_.TimedWait(time_remaining);
+ time_remaining -= base::TimeTicks::HighResNow() - start;
+ }
+
+ return InternalPop(frame);
+}
+
+bool SpdyFramePriorityQueue::InternalPop(net::SpdyFrameIR** frame) {
+ lock_.AssertAcquired();
+ DCHECK(frame);
+ if (queue_map_.empty()) {
+ return false;
+ }
+ // As an invariant, the lists in the queue map are never empty. So get the
+ // list of highest priority (smallest priority number) and pop the first
+ // frame from it.
+ QueueMap::iterator iter = queue_map_.begin();
+ FrameList* list = iter->second;
+ DCHECK(!list->empty());
+ *frame = list->front();
+ list->pop_front();
+ // If the list is now empty, we have to delete it from the map to maintain
+ // the invariant.
+ if (list->empty()) {
+ queue_map_.erase(iter);
+ delete list;
+ }
+ return true;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
+#define MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace base { class TimeDelta; }
+
+namespace net { class SpdyFrameIR; }
+
+namespace mod_spdy {
+
+// A priority queue of SPDY frames, intended for multiplexing output frames
+// from multiple SPDY stream threads back to the SPDY connection thread and
+// allowing frames from high-priority streams to cut in front of lower-priority
+// streams. This class is thread-safe -- its methods may be called
+// concurrently by multiple threads.
+class SpdyFramePriorityQueue {
+ public:
+ // Create an initially-empty queue.
+ SpdyFramePriorityQueue();
+ ~SpdyFramePriorityQueue();
+
+ // Return true if the queue is currently empty. (Of course, there's no
+ // guarantee that another thread won't change that as soon as this method
+ // returns.)
+ bool IsEmpty() const;
+
+ // A priority value that is more important than any priority normally used
+ // for sending SPDY frames.
+ static const int kTopPriority;
+
+ // Insert a frame into the queue at the specified priority. The queue takes
+ // ownership of the frame, and will delete it if the queue is deleted before
+ // the frame is removed from the queue by the Pop method. Note that smaller
+ // numbers indicate higher priorities.
+ void Insert(int priority, net::SpdyFrameIR* frame);
+
+ // Remove and provide a frame from the queue and return true, or return false
+ // if the queue is empty. The caller gains ownership of the provided frame
+ // object. This method will try to yield higher-priority frames before
+ // lower-priority ones (even if they were inserted later), but guarantees to
+ // return same-priority frames in the same order they were inserted (FIFO).
+ // In particular, this means that a sequence of frames from the same SPDY
+ // stream will stay in order (assuming they were all inserted with the same
+ // priority -- that of the stream).
+ bool Pop(net::SpdyFrameIR** frame);
+
+ // Like Pop(), but if the queue is empty this method will block for up to
+ // max_time before returning false.
+ bool BlockingPop(const base::TimeDelta& max_time, net::SpdyFrameIR** frame);
+
+ private:
+ // Same as Pop(), but requires lock_ to be held.
+ bool InternalPop(net::SpdyFrameIR** frame);
+
+ mutable base::Lock lock_;
+ base::ConditionVariable condvar_;
+ // We use a map of lists to store frames, to guarantee that frames of the
+ // same priority are stored in FIFO order. A simpler implementation would be
+ // to just use a multimap, which in practice is nearly always implemented
+ // with the FIFO behavior that we want, but the spec doesn't actually
+ // guarantee that behavior.
+ //
+ // Each list stores frames of a particular priority. Invariant: the lists in
+ // the QueueMap are never empty; if one of the lists becomes empty, that
+ // key/value pair is immediately removed from the map.
+ typedef std::list<net::SpdyFrameIR*> FrameList;
+ typedef std::map<int, FrameList*> QueueMap;
+ QueueMap queue_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFramePriorityQueue);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+
+#include "base/time/time.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void ExpectPop(net::SpdyPingId expected,
+ mod_spdy::SpdyFramePriorityQueue* queue) {
+ EXPECT_FALSE(queue->IsEmpty());
+ net::SpdyFrameIR* raw_frame = NULL;
+ const bool success = queue->Pop(&raw_frame);
+ scoped_ptr<net::SpdyFrameIR> scoped_frame(raw_frame);
+ EXPECT_TRUE(success);
+ ASSERT_TRUE(scoped_frame != NULL);
+ EXPECT_THAT(*scoped_frame, mod_spdy::testing::IsPing(expected));
+}
+
+void ExpectEmpty(mod_spdy::SpdyFramePriorityQueue* queue) {
+ EXPECT_TRUE(queue->IsEmpty());
+ net::SpdyFrameIR* frame = NULL;
+ EXPECT_FALSE(queue->Pop(&frame));
+ EXPECT_TRUE(frame == NULL);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertSpdy2) {
+ net::SpdyFramer framer(net::SPDY2);
+ mod_spdy::SpdyFramePriorityQueue queue;
+ ExpectEmpty(&queue);
+
+ EXPECT_EQ(3u, framer.GetLowestPriority());
+ EXPECT_EQ(0u, framer.GetHighestPriority());
+
+ queue.Insert(3, new net::SpdyPingIR(4));
+ queue.Insert(0, new net::SpdyPingIR(1));
+ queue.Insert(3, new net::SpdyPingIR(3));
+
+ ExpectPop(1, &queue);
+ ExpectPop(4, &queue);
+
+ queue.Insert(2, new net::SpdyPingIR(2));
+ queue.Insert(1, new net::SpdyPingIR(6));
+ queue.Insert(1, new net::SpdyPingIR(5));
+
+ ExpectPop(6, &queue);
+ ExpectPop(5, &queue);
+ ExpectPop(2, &queue);
+ ExpectPop(3, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertSpdy3) {
+ net::SpdyFramer framer(net::SPDY3);
+ mod_spdy::SpdyFramePriorityQueue queue;
+ ExpectEmpty(&queue);
+
+ EXPECT_EQ(7u, framer.GetLowestPriority());
+ EXPECT_EQ(0u, framer.GetHighestPriority());
+
+ queue.Insert(7, new net::SpdyPingIR(4));
+ queue.Insert(0, new net::SpdyPingIR(1));
+ queue.Insert(7, new net::SpdyPingIR(3));
+
+ ExpectPop(1, &queue);
+ ExpectPop(4, &queue);
+
+ queue.Insert(6, new net::SpdyPingIR(2));
+ queue.Insert(1, new net::SpdyPingIR(6));
+ queue.Insert(5, new net::SpdyPingIR(5));
+
+ ExpectPop(6, &queue);
+ ExpectPop(5, &queue);
+ ExpectPop(2, &queue);
+ ExpectPop(3, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertTopPriority) {
+ mod_spdy::SpdyFramePriorityQueue queue;
+ ExpectEmpty(&queue);
+
+ queue.Insert(3, new net::SpdyPingIR(4));
+ queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyPingIR(2));
+ queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyPingIR(6));
+ queue.Insert(0, new net::SpdyPingIR(1));
+ queue.Insert(3, new net::SpdyPingIR(3));
+
+ ExpectPop(2, &queue);
+ ExpectPop(6, &queue);
+ ExpectPop(1, &queue);
+ ExpectPop(4, &queue);
+
+ queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyPingIR(5));
+
+ ExpectPop(5, &queue);
+ ExpectPop(3, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, BlockingPop) {
+ mod_spdy::SpdyFramePriorityQueue queue;
+ net::SpdyFrameIR* frame;
+ ASSERT_FALSE(queue.Pop(&frame));
+
+ const base::TimeDelta time_to_wait = base::TimeDelta::FromMilliseconds(50);
+ const base::TimeTicks start = base::TimeTicks::HighResNow();
+ ASSERT_FALSE(queue.BlockingPop(time_to_wait, &frame));
+ const base::TimeDelta actual_time_waited =
+ base::TimeTicks::HighResNow() - start;
+
+ // Check that we waited at least as long as we asked for.
+ EXPECT_GE(actual_time_waited, time_to_wait);
+ // Check that we didn't wait too much longer than we asked for.
+ EXPECT_LT(actual_time_waited.InMillisecondsF(),
+ 1.1 * time_to_wait.InMillisecondsF());
+}
+
+} // namespace
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_frame_queue.h"
+
+#include <list>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SpdyFrameQueue::SpdyFrameQueue()
+ : condvar_(&lock_), is_aborted_(false) {}
+
+SpdyFrameQueue::~SpdyFrameQueue() {
+ STLDeleteContainerPointers(queue_.begin(), queue_.end());
+}
+
+bool SpdyFrameQueue::is_aborted() const {
+ base::AutoLock autolock(lock_);
+ return is_aborted_;
+}
+
+void SpdyFrameQueue::Abort() {
+ base::AutoLock autolock(lock_);
+ is_aborted_ = true;
+ STLDeleteContainerPointers(queue_.begin(), queue_.end());
+ queue_.clear();
+ condvar_.Broadcast();
+}
+
+void SpdyFrameQueue::Insert(net::SpdyFrameIR* frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ if (is_aborted_) {
+ DCHECK(queue_.empty());
+ delete frame;
+ } else {
+ if (queue_.empty()) {
+ condvar_.Signal();
+ }
+ queue_.push_front(frame);
+ }
+}
+
+bool SpdyFrameQueue::Pop(bool block, net::SpdyFrameIR** frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ if (block) {
+ // Block until the queue is nonempty or we abort.
+ while (queue_.empty() && !is_aborted_) {
+ condvar_.Wait();
+ }
+ }
+
+ // If we've aborted, the queue should now be empty.
+ DCHECK(!is_aborted_ || queue_.empty());
+ if (queue_.empty()) {
+ return false;
+ }
+
+ *frame = queue_.back();
+ queue_.pop_back();
+ return true;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
+#define MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace net { class SpdyFrameIR; }
+
+namespace mod_spdy {
+
+// A simple FIFO queue of SPDY frames, intended for sending input frames from
+// the SPDY connection thread to a SPDY stream thread. This class is
+// thread-safe -- all methods may be called concurrently by multiple threads.
+class SpdyFrameQueue {
+ public:
+ // Create an initially-empty queue.
+ SpdyFrameQueue();
+ ~SpdyFrameQueue();
+
+ // Return true if this queue has been aborted.
+ bool is_aborted() const;
+
+ // Abort the queue. All frames held by the queue will be deleted; future
+ // frames passed to Insert() will be immediately deleted; future calls to
+ // Pop() will fail immediately; and current blocking calls to Pop will
+ // immediately unblock and fail.
+ void Abort();
+
+ // Insert a frame into the queue. The queue takes ownership of the frame,
+ // and will delete it if the queue is deleted or aborted before the frame is
+ // removed from the queue by the Pop method.
+ void Insert(net::SpdyFrameIR* frame);
+
+ // Remove and provide a frame from the queue and return true, or return false
+ // if the queue is empty or has been aborted. If the block argument is true,
+ // block until a frame becomes available (or the queue is aborted). The
+ // caller gains ownership of the provided frame object.
+ bool Pop(bool block, net::SpdyFrameIR** frame);
+
+ private:
+ // This is a pretty naive implementation of a thread-safe queue, but it's
+ // good enough for our purposes. We could use an apr_queue_t instead of
+ // rolling our own class, but it lacks the ownership semantics that we want.
+ mutable base::Lock lock_;
+ base::ConditionVariable condvar_;
+ std::list<net::SpdyFrameIR*> queue_;
+ bool is_aborted_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameQueue);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_frame_queue.h"
+
+#include "base/basictypes.h"
+#include "base/threading/platform_thread.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int kSpdyVersion = 2;
+
+void ExpectPop(bool block, net::SpdyStreamId expected,
+ mod_spdy::SpdyFrameQueue* queue) {
+ net::SpdyFrameIR* raw_frame = NULL;
+ const bool success = queue->Pop(block, &raw_frame);
+ scoped_ptr<net::SpdyFrameIR> scoped_frame(raw_frame);
+ EXPECT_TRUE(success);
+ ASSERT_TRUE(scoped_frame != NULL);
+ EXPECT_THAT(*scoped_frame, mod_spdy::testing::IsPing(expected));
+}
+
+void ExpectEmpty(mod_spdy::SpdyFrameQueue* queue) {
+ net::SpdyFrameIR* frame = NULL;
+ EXPECT_FALSE(queue->Pop(false, &frame));
+ EXPECT_TRUE(frame == NULL);
+}
+
+TEST(SpdyFrameQueueTest, Simple) {
+ mod_spdy::SpdyFrameQueue queue;
+ ExpectEmpty(&queue);
+
+ queue.Insert(new net::SpdyPingIR(4));
+ queue.Insert(new net::SpdyPingIR(1));
+ queue.Insert(new net::SpdyPingIR(3));
+
+ ExpectPop(false, 4, &queue);
+ ExpectPop(false, 1, &queue);
+
+ queue.Insert(new net::SpdyPingIR(2));
+ queue.Insert(new net::SpdyPingIR(5));
+
+ ExpectPop(false, 3, &queue);
+ ExpectPop(false, 2, &queue);
+ ExpectPop(false, 5, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFrameQueueTest, AbortEmptiesQueue) {
+ mod_spdy::SpdyFrameQueue queue;
+ ASSERT_FALSE(queue.is_aborted());
+ ExpectEmpty(&queue);
+
+ queue.Insert(new net::SpdyPingIR(4));
+ queue.Insert(new net::SpdyPingIR(1));
+ queue.Insert(new net::SpdyPingIR(3));
+
+ ExpectPop(false, 4, &queue);
+
+ queue.Abort();
+
+ ExpectEmpty(&queue);
+ ASSERT_TRUE(queue.is_aborted());
+}
+
+class BlockingPopTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+ explicit BlockingPopTask(mod_spdy::SpdyFrameQueue* queue) : queue_(queue) {}
+ virtual void Run() { ExpectPop(true, 7, queue_); }
+ private:
+ mod_spdy::SpdyFrameQueue* const queue_;
+ DISALLOW_COPY_AND_ASSIGN(BlockingPopTask);
+};
+
+TEST(SpdyFrameQueueTest, BlockingPop) {
+ mod_spdy::SpdyFrameQueue queue;
+
+ // Start a task that will do a blocking pop from the queue.
+ mod_spdy::testing::AsyncTaskRunner runner(new BlockingPopTask(&queue));
+ ASSERT_TRUE(runner.Start());
+
+ // Even if we wait for a little bit, the task shouldn't complete, because
+ // that thread is blocked, because the queue is still empty.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+ runner.notification()->ExpectNotSet();
+ ExpectEmpty(&queue);
+
+ // Now, if we push something into the queue, the task should soon unblock and
+ // complete, and the queue should then be empty.
+ queue.Insert(new net::SpdyPingIR(7));
+ runner.notification()->ExpectSetWithinMillis(100);
+ ExpectEmpty(&queue);
+}
+
+} // namespace
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_server_config.h"
+
+#include "mod_spdy/common/protocol_util.h"
+
+namespace {
+
+const bool kDefaultSpdyEnabled = false;
+const int kDefaultMaxStreamsPerConnection = 100;
+const int kDefaultMinThreadsPerProcess = 2;
+const int kDefaultMaxThreadsPerProcess = 10;
+const int kDefaultMaxServerPushDepth = 1;
+const bool kDefaultSendVersionHeader = true;
+const mod_spdy::spdy::SpdyVersion kDefaultUseSpdyVersionWithoutSsl =
+ mod_spdy::spdy::SPDY_VERSION_NONE;
+const int kDefaultVlogLevel = 0;
+
+} // namespace
+
+namespace mod_spdy {
+
+SpdyServerConfig::SpdyServerConfig()
+ : spdy_enabled_(kDefaultSpdyEnabled),
+ max_streams_per_connection_(kDefaultMaxStreamsPerConnection),
+ min_threads_per_process_(kDefaultMinThreadsPerProcess),
+ max_threads_per_process_(kDefaultMaxThreadsPerProcess),
+ max_server_push_depth_(kDefaultMaxServerPushDepth),
+ send_version_header_(kDefaultSendVersionHeader),
+ use_spdy_version_without_ssl_(kDefaultUseSpdyVersionWithoutSsl),
+ vlog_level_(kDefaultVlogLevel) {}
+
+SpdyServerConfig::~SpdyServerConfig() {}
+
+void SpdyServerConfig::MergeFrom(const SpdyServerConfig& a,
+ const SpdyServerConfig& b) {
+ spdy_enabled_.MergeFrom(a.spdy_enabled_, b.spdy_enabled_);
+ max_streams_per_connection_.MergeFrom(a.max_streams_per_connection_,
+ b.max_streams_per_connection_);
+ min_threads_per_process_.MergeFrom(a.min_threads_per_process_,
+ b.min_threads_per_process_);
+ max_threads_per_process_.MergeFrom(a.max_threads_per_process_,
+ b.max_threads_per_process_);
+ max_server_push_depth_.MergeFrom(a.max_server_push_depth_,
+ b.max_server_push_depth_);
+ send_version_header_.MergeFrom(
+ a.send_version_header_, b.send_version_header_);
+ use_spdy_version_without_ssl_.MergeFrom(
+ a.use_spdy_version_without_ssl_, b.use_spdy_version_without_ssl_);
+ vlog_level_.MergeFrom(a.vlog_level_, b.vlog_level_);
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SERVER_CONFIG_H_
+#define MOD_SPDY_COMMON_SPDY_SERVER_CONFIG_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace mod_spdy {
+
+// Stores server configuration settings for our module.
+class SpdyServerConfig {
+ public:
+ SpdyServerConfig();
+ ~SpdyServerConfig();
+
+ // Return true if SPDY is enabled for this server, false otherwise.
+ bool spdy_enabled() const { return spdy_enabled_.get(); }
+
+ // Return the maximum number of simultaneous SPDY streams that should be
+ // permitted for a single client connection.
+ int max_streams_per_connection() const {
+ return max_streams_per_connection_.get();
+ }
+
+ // Return the minimum number of worker threads to spawn per child process.
+ int min_threads_per_process() const {
+ return min_threads_per_process_.get();
+ }
+
+ // Return the maximum number of worker threads to spawn per child process.
+ int max_threads_per_process() const {
+ return max_threads_per_process_.get();
+ }
+
+ // Return the maximum number of recursive levels to follow
+ // X-Associated-Content headers
+ int max_server_push_depth() const {
+ return max_server_push_depth_.get();
+ }
+
+ // Whether or not we should include an x-mod-spdy header with the module
+ // version number.
+ bool send_version_header() const { return send_version_header_.get(); }
+
+ // If nonzero, assume (unencrypted) SPDY/x for non-SSL connections, where x
+ // is the version number returned here. This will most likely break normal
+ // browsers, but is useful for testing.
+ spdy::SpdyVersion use_spdy_version_without_ssl() const {
+ return use_spdy_version_without_ssl_.get();
+ }
+
+ // Return the maximum VLOG level we should use.
+ int vlog_level() const { return vlog_level_.get(); }
+
+ // Setters. Call only during the configuration phase.
+ void set_spdy_enabled(bool b) { spdy_enabled_.set(b); }
+ void set_max_streams_per_connection(int n) {
+ max_streams_per_connection_.set(n);
+ }
+ void set_min_threads_per_process(int n) { min_threads_per_process_.set(n); }
+ void set_max_threads_per_process(int n) { max_threads_per_process_.set(n); }
+ void set_max_server_push_depth(int n) { max_server_push_depth_.set(n); }
+ void set_send_version_header(bool b) { send_version_header_.set(b); }
+ void set_use_spdy_version_without_ssl(spdy::SpdyVersion v) {
+ use_spdy_version_without_ssl_.set(v);
+ }
+ void set_vlog_level(int n) { vlog_level_.set(n); }
+
+ // Set this config object to the merge of a and b. Call only during the
+ // configuration phase.
+ void MergeFrom(const SpdyServerConfig& a, const SpdyServerConfig& b);
+
+ private:
+ template <typename T>
+ class Option {
+ public:
+ explicit Option(const T& default_value)
+ : was_set_(false), value_(default_value) {}
+ const T& get() const { return value_; }
+ void set(const T& value) { was_set_ = true; value_ = value; }
+ void MergeFrom(const Option<T>& a, const Option<T>& b) {
+ was_set_ = a.was_set_ || b.was_set_;
+ value_ = a.was_set_ ? a.value_ : b.value_;
+ }
+ private:
+ bool was_set_;
+ T value_;
+ DISALLOW_COPY_AND_ASSIGN(Option);
+ };
+
+ // Configuration fields:
+ Option<bool> spdy_enabled_;
+ Option<int> max_streams_per_connection_;
+ Option<int> min_threads_per_process_;
+ Option<int> max_threads_per_process_;
+ Option<int> max_server_push_depth_;
+ Option<bool> send_version_header_;
+ Option<spdy::SpdyVersion> use_spdy_version_without_ssl_;
+ Option<int> vlog_level_;
+ // Note: Add more config options here as needed; be sure to also update the
+ // MergeFrom method in spdy_server_config.cc.
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyServerConfig);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_CONTEXT_SPDY_SERVER_CONFIG_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_server_push_interface.h"
+
+namespace mod_spdy {
+
+SpdyServerPushInterface::SpdyServerPushInterface() {}
+
+SpdyServerPushInterface::~SpdyServerPushInterface() {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
+#define MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class SpdyServerPushInterface {
+ public:
+ SpdyServerPushInterface();
+ virtual ~SpdyServerPushInterface();
+
+ enum PushStatus {
+ // PUSH_STARTED: The server push was started successfully.
+ PUSH_STARTED,
+ // INVALID_REQUEST_HEADERS: The given request headers were invalid for a
+ // server push (e.g. because required headers were missing).
+ INVALID_REQUEST_HEADERS,
+ // ASSOCIATED_STREAM_INACTIVE: The push could not be started because the
+ // associated stream is not currently active.
+ ASSOCIATED_STREAM_INACTIVE,
+ // CANNOT_PUSH_EVER_AGAIN: We can't do any more pushes on this session,
+ // either because the client has already sent us a GOAWAY frame, or the
+ // session has been open so long that we've run out of stream IDs.
+ CANNOT_PUSH_EVER_AGAIN,
+ // TOO_MANY_CONCURRENT_PUSHES: The push could not be started right now
+ // because there are too many currently active push streams.
+ TOO_MANY_CONCURRENT_PUSHES,
+ // PUSH_INTERNAL_ERROR: There was an internal error in the SpdySession
+ // (typically something that caused a LOG(DFATAL).
+ PUSH_INTERNAL_ERROR,
+ };
+
+ // Initiate a SPDY server push, roughly by pretending that the client sent a
+ // SYN_STREAM with the given headers. To repeat: the headers argument is
+ // _not_ the headers that the server will send to the client, but rather the
+ // headers to _pretend_ that the client sent to the server.
+ virtual PushStatus StartServerPush(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyServerPushInterface);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_session.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_session_io.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/spdy_stream_task_factory.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// Server push stream IDs must be even, and must fit in 31 bits (SPDY draft 3
+// section 2.3.2). Thus, this is the largest stream ID we can ever use for a
+// pushed stream.
+const net::SpdyStreamId kMaxServerPushStreamId = 0x7FFFFFFEu;
+
+// Until the client informs us otherwise, we will assume a limit of 100 open
+// push streams at a time.
+const uint32 kInitMaxConcurrentPushes = 100u;
+
+} // namespace
+
+namespace mod_spdy {
+
+SpdySession::SpdySession(spdy::SpdyVersion spdy_version,
+ const SpdyServerConfig* config,
+ SpdySessionIO* session_io,
+ SpdyStreamTaskFactory* task_factory,
+ Executor* executor)
+ : spdy_version_(spdy_version),
+ config_(config),
+ session_io_(session_io),
+ task_factory_(task_factory),
+ executor_(executor),
+ framer_(SpdyVersionToFramerVersion(spdy_version), true),
+ session_stopped_(false),
+ already_sent_goaway_(false),
+ last_client_stream_id_(0u),
+ initial_window_size_(net::kSpdyStreamInitialWindowSize),
+ max_concurrent_pushes_(kInitMaxConcurrentPushes),
+ last_server_push_stream_id_(0u),
+ received_goaway_(false),
+ shared_window_(net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize) {
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ framer_.set_visitor(this);
+}
+
+SpdySession::~SpdySession() {}
+
+int32 SpdySession::current_shared_input_window_size() const {
+ DCHECK_GE(spdy_version_, spdy::SPDY_VERSION_3_1);
+ return shared_window_.current_input_window_size();
+}
+
+int32 SpdySession::current_shared_output_window_size() const {
+ DCHECK_GE(spdy_version_, spdy::SPDY_VERSION_3_1);
+ return shared_window_.current_output_window_size();
+}
+
+void SpdySession::Run() {
+ // Send a SETTINGS frame when the connection first opens, to inform the
+ // client of our MAX_CONCURRENT_STREAMS limit.
+ SendSettingsFrame();
+
+ // Initial amount time to block when waiting for output -- we start with
+ // this, and as long as we fail to perform any input OR output, we increase
+ // exponentially to the max, resetting when we succeed again.
+ const base::TimeDelta kInitOutputBlockTime =
+ base::TimeDelta::FromMilliseconds(1);
+ // Maximum time to block when waiting for output.
+ const base::TimeDelta kMaxOutputBlockTime =
+ base::TimeDelta::FromMilliseconds(30);
+
+ base::TimeDelta output_block_time = kInitOutputBlockTime;
+
+ // Until we stop the session, or it is aborted by the client, alternate
+ // between reading input from the client and (compressing and) sending output
+ // frames that our stream threads have posted to the output queue. This
+ // basically amounts to a busy-loop, switching back and forth between input
+ // and output, so we do our best to block when we can. It would be far nicer
+ // to have separate threads for input and output and have them always block;
+ // unfortunately, we cannot do that, because in Apache the input and output
+ // filter chains for a connection must be invoked by the same thread.
+ while (!session_stopped_) {
+ if (session_io_->IsConnectionAborted()) {
+ LOG(WARNING) << "Master connection was aborted.";
+ StopSession();
+ break;
+ }
+
+ // Step 1: Read input from the client.
+ {
+ // Determine whether we should block until more input data is available.
+ // For now, our policy is to block only if there is no pending output and
+ // there are no currently-active streams (which might produce new
+ // output).
+ const bool should_block = StreamMapIsEmpty() && output_queue_.IsEmpty();
+
+ // If there's no current output, and we can't create new streams (so
+ // there will be no future output), then we should just shut down the
+ // connection.
+ if (should_block && already_sent_goaway_) {
+ StopSession();
+ break;
+ }
+
+ // Read available input data. The SpdySessionIO will grab any
+ // available data and push it into the SpdyFramer that we pass to it
+ // here; the SpdyFramer, in turn, will call our OnControl and/or
+ // OnStreamFrameData methods to report decoded frames. If no input data
+ // is currently available and should_block is true, this will block until
+ // input becomes available (or the connection is closed).
+ const SpdySessionIO::ReadStatus status =
+ session_io_->ProcessAvailableInput(should_block, &framer_);
+ if (status == SpdySessionIO::READ_SUCCESS) {
+ // We successfully did some I/O, so reset the output block timeout.
+ output_block_time = kInitOutputBlockTime;
+ } else if (status == SpdySessionIO::READ_CONNECTION_CLOSED) {
+ // The reading side of the connection has closed, so we won't be
+ // reading anything more. SPDY is transport-layer agnostic and not
+ // TCP-specific; apparently, this means that there is no expectation
+ // that we behave any differently for a half-closed connection than for
+ // a fully-closed connection. So if the reading side of the connection
+ // closes, we're just going to shut down completely.
+ //
+ // But just in case the writing side is still open, let's try to send a
+ // GOAWAY to let the client know we're shutting down gracefully.
+ SendGoAwayFrame(net::GOAWAY_OK);
+ // Now, shut everything down.
+ StopSession();
+ } else if (status == SpdySessionIO::READ_ERROR) {
+ // There was an error during reading, so the session is corrupted and
+ // we have no chance of reading anything more.
+ //
+ // We've probably already sent a GOAWAY with a PROTOCOL_ERROR by this
+ // point, but if we haven't (perhaps the error was our fault?) then
+ // send a GOAWAY now. (If we've already sent a GOAWAY, then
+ // SendGoAwayFrame is a no-op.)
+ SendGoAwayFrame(net::GOAWAY_INTERNAL_ERROR);
+ // Now, shut everything down.
+ StopSession();
+ } else {
+ // Otherwise, there's simply no data available at the moment.
+ DCHECK_EQ(SpdySessionIO::READ_NO_DATA, status);
+ }
+ }
+
+ // Step 2: Send output to the client.
+ if (!session_stopped_) {
+ // If there are no active streams, then no new output can be getting
+ // created right now, so we shouldn't block on output waiting for more.
+ const bool no_active_streams = StreamMapIsEmpty();
+
+ // Send any pending output, one frame at a time. If there are any active
+ // streams, we're willing to block briefly to wait for more frames to
+ // send, if only to prevent this loop from busy-waiting too heavily --
+ // not a great solution, but better than nothing for now.
+ net::SpdyFrameIR* frame = NULL;
+ if (no_active_streams ? output_queue_.Pop(&frame) :
+ output_queue_.BlockingPop(output_block_time, &frame)) {
+ do {
+ SendFrame(frame);
+ } while (!session_stopped_ && output_queue_.Pop(&frame));
+
+ // We successfully did some I/O, so reset the output block timeout.
+ output_block_time = kInitOutputBlockTime;
+ } else {
+ // The queue is currently empty; if no more streams can be created and
+ // no more remain, we're done.
+ if (already_sent_goaway_ && no_active_streams) {
+ StopSession();
+ } else {
+ // There were no output frames within the timeout; so do an
+ // exponential backoff by doubling output_block_time.
+ output_block_time = std::min(kMaxOutputBlockTime,
+ output_block_time * 2);
+ }
+ }
+ }
+
+ // TODO(mdsteele): What we really want to be able to do is to block until
+ // *either* more input or more output is available. Unfortunely, there's
+ // no good way to query the input side (in Apache). One possibility would
+ // be to grab the input socket object (which we can do), and then arrange
+ // to block until either the socket is ready to read OR our output queue is
+ // nonempty (obviously we would abstract that away in SpdySessionIO),
+ // but there's not even a nice way to do that (that I know of).
+ }
+}
+
+SpdyServerPushInterface::PushStatus SpdySession::StartServerPush(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers) {
+ // Server push is pretty ill-defined in SPDY v2, so we require v3 or higher.
+ DCHECK_GE(spdy_version(), spdy::SPDY_VERSION_3);
+
+ // Grab the headers that we are required to send with the initial SYN_STREAM.
+ const net::SpdyHeaderBlock::const_iterator host_iter =
+ request_headers.find(spdy::kSpdy3Host);
+ const net::SpdyHeaderBlock::const_iterator path_iter =
+ request_headers.find(spdy::kSpdy3Path);
+ const net::SpdyHeaderBlock::const_iterator scheme_iter =
+ request_headers.find(spdy::kSpdy3Scheme);
+ if (host_iter == request_headers.end() ||
+ path_iter == request_headers.end() ||
+ scheme_iter == request_headers.end()) {
+ return SpdyServerPushInterface::INVALID_REQUEST_HEADERS;
+ }
+ const std::string& host_header = host_iter->second;
+ const std::string& path_header = path_iter->second;
+ const std::string& scheme_header = scheme_iter->second;
+
+ StreamTaskWrapper* task_wrapper = NULL;
+ {
+ base::AutoLock autolock(stream_map_lock_);
+
+ // If we've received a GOAWAY frame the client, we shouldn't create any new
+ // streams on this session (SPDY draft 3 section 2.6.6).
+ if (received_goaway_) {
+ return SpdyServerPushInterface::CANNOT_PUSH_EVER_AGAIN;
+ }
+
+ // The associated stream must be active (SPDY draft 3 section 3.3.1).
+ if (!stream_map_.IsStreamActive(associated_stream_id)) {
+ return SpdyServerPushInterface::ASSOCIATED_STREAM_INACTIVE;
+ }
+
+ // Check if we're allowed to create new push streams right now (based on
+ // the client SETTINGS_MAX_CONCURRENT_STREAMS). Note that the number of
+ // active push streams might be (temporarily) greater than the max, if the
+ // client lowered the max after we already started a bunch of pushes.
+ if (stream_map_.NumActivePushStreams() >= max_concurrent_pushes_) {
+ return SpdyServerPushInterface::TOO_MANY_CONCURRENT_PUSHES;
+ }
+
+ // In the unlikely event that the session stays open so long that we run
+ // out of server push stream IDs, we may not do any more pushes on this
+ // session (SPDY draft 3 section 2.3.2).
+ DCHECK_LE(last_server_push_stream_id_, kMaxServerPushStreamId);
+ if (last_server_push_stream_id_ >= kMaxServerPushStreamId) {
+ return SpdyServerPushInterface::CANNOT_PUSH_EVER_AGAIN;
+ }
+ // Server push stream IDs must be even (SPDY draft 3 section 2.3.2). So
+ // each time we do a push, we increment last_server_push_stream_id_ by two.
+ DCHECK_EQ(last_server_push_stream_id_ % 2u, 0u);
+ last_server_push_stream_id_ += 2u;
+ const net::SpdyStreamId stream_id = last_server_push_stream_id_;
+ // Only the server can create even stream IDs, and we never use the same
+ // one twice, so our chosen stream_id should definitely not be in use.
+ if (stream_map_.IsStreamActive(stream_id)) {
+ LOG(DFATAL) << "Next server push stream ID already in use: "
+ << stream_id;
+ return SpdyServerPushInterface::PUSH_INTERNAL_ERROR;
+ }
+
+ // Create task and add it to the stream map.
+ task_wrapper = new StreamTaskWrapper(
+ this, stream_id, associated_stream_id, server_push_depth, priority);
+ stream_map_.AddStreamTask(task_wrapper);
+ net::SpdySynStreamIR* frame = new net::SpdySynStreamIR(stream_id);
+ frame->set_associated_to_stream_id(associated_stream_id);
+ frame->set_priority(priority);
+ frame->set_fin(true);
+ frame->GetMutableNameValueBlock()->insert(
+ request_headers.begin(), request_headers.end());
+ task_wrapper->stream()->PostInputFrame(frame);
+
+ // Send initial SYN_STREAM to the client. It only needs to contain the
+ // ":host", ":path", and ":scheme" headers; the rest can follow in a later
+ // HEADERS frame (SPDY draft 3 section 3.3.1).
+ net::SpdyHeaderBlock initial_response_headers;
+ initial_response_headers[spdy::kSpdy3Host] = host_header;
+ initial_response_headers[spdy::kSpdy3Path] = path_header;
+ initial_response_headers[spdy::kSpdy3Scheme] = scheme_header;
+ task_wrapper->stream()->SendOutputSynStream(
+ initial_response_headers, false);
+
+ VLOG(2) << "Starting server push; opening stream " << stream_id;
+ }
+ if (task_wrapper == NULL) {
+ LOG(DFATAL) << "Can't happen: task_wrapper is NULL";
+ return SpdyServerPushInterface::PUSH_INTERNAL_ERROR;
+ }
+ executor_->AddTask(task_wrapper, priority);
+ return SpdyServerPushInterface::PUSH_STARTED;
+}
+
+void SpdySession::OnError(net::SpdyFramer::SpdyError error_code) {
+ LOG(ERROR) << "Session error: "
+ << net::SpdyFramer::ErrorCodeToString(error_code);
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+}
+
+void SpdySession::OnStreamError(net::SpdyStreamId stream_id,
+ const std::string& description) {
+ LOG(ERROR) << "Stream " << stream_id << " error: " << description;
+ AbortStream(stream_id, net::RST_STREAM_PROTOCOL_ERROR);
+}
+
+void SpdySession::OnStreamFrameData(
+ net::SpdyStreamId stream_id, const char* data, size_t length, bool fin) {
+ // First check the shared input flow control window (for SPDY/3.1 and up).
+ if (spdy_version_ >= spdy::SPDY_VERSION_3_1) {
+ if (!shared_window_.OnReceiveInputData(length)) {
+ LOG(ERROR) << "Client violated flow control by sending too much data "
+ << "to session. Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ StopSession();
+ return;
+ }
+ }
+
+ // Look up the stream to post the data to. We need to lock when reading the
+ // stream map, because one of the stream threads could call
+ // RemoveStreamTask() at any time.
+ {
+ base::AutoLock autolock(stream_map_lock_);
+ SpdyStream* stream = stream_map_.GetStream(stream_id);
+ if (stream != NULL) {
+ VLOG(4) << "[stream " << stream_id << "] Received DATA (length="
+ << length << ")";
+ // Copy the data into an _uncompressed_ SPDY data frame and post it to
+ // the stream's input queue.
+ // Note that we must still be holding stream_map_lock_ when we call this
+ // method -- otherwise the stream may be deleted out from under us by the
+ // StreamTaskWrapper destructor. That's okay -- PostInputFrame is a
+ // quick operation and won't block (for any appreciable length of time).
+ net::SpdyDataIR* frame =
+ new net::SpdyDataIR(stream_id, base::StringPiece(data, length));
+ frame->set_fin(fin);
+ stream->PostInputFrame(frame);
+ return;
+ }
+ }
+
+ // If we reach this point, it means that the client has sent us DATA for a
+ // stream that doesn't exist (possibly because it used to exist but has
+ // already been closed by a FLAG_FIN); *unless* length=0, which is just the
+ // BufferedSpdyFramer's way of telling us that there will be no more data on
+ // this stream (i.e. because a FLAG_FIN has been received, possibly on a
+ // previous control frame).
+
+ // TODO(mdsteele): The BufferedSpdyFramer sends us OnStreamFrameData with
+ // length=0 to indicate end-of-stream, but it will do this even if we already
+ // got FLAG_FIN in a control frame (such as SYN_STREAM). For now, we fix
+ // this issue by simply ignoring length=0 data for streams that no longer
+ // exist. Once we transition to the new plain SpdyFramer, we'll be able to
+ // handle this more precisely.
+ if (length == 0) {
+ return;
+ }
+
+ // If the client sends data for a nonexistant stream, we must send a
+ // RST_STREAM frame with error code INVALID_STREAM (SPDY draft 2 section
+ // 2.4). Note that we release the mutex *before* sending the frame.
+ LOG(WARNING) << "Client sent DATA (length=" << length
+ << ") for nonexistant stream " << stream_id;
+ SendRstStreamFrame(stream_id, net::RST_STREAM_INVALID_STREAM);
+}
+
+void SpdySession::OnSynStream(
+ net::SpdyStreamId stream_id,
+ net::SpdyStreamId associated_stream_id,
+ net::SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const net::SpdyHeaderBlock& headers) {
+ // The SPDY spec requires us to ignore SYN_STREAM frames after sending a
+ // GOAWAY frame (SPDY draft 3 section 2.6.6).
+ if (already_sent_goaway_) {
+ return;
+ }
+
+ // Client stream IDs must be odd-numbered.
+ if (stream_id % 2 == 0) {
+ LOG(WARNING) << "Client sent SYN_STREAM for even stream ID (" << stream_id
+ << "). Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ return;
+ }
+
+ // Client stream IDs must be strictly increasing (SPDY draft 2 section
+ // 2.5.1).
+ if (stream_id <= last_client_stream_id_) {
+ LOG(WARNING) << "Client sent SYN_STREAM for non-increasing stream ID ("
+ << stream_id << " after " << last_client_stream_id_
+ << ")."; // Aborting stream.";
+#if 0
+ // TODO(mdsteele): re-enable this code block when
+ // http://code.google.com/p/chromium/issues/detail?id=111708 is
+ // fixed.
+ AbortStream(stream_id, net::PROTOCOL_ERROR);
+ return;
+#endif
+ }
+
+ StreamTaskWrapper* task_wrapper = NULL;
+ {
+ // Lock the stream map before we start checking its size or adding a new
+ // stream to it. We need to lock when touching the stream map, because one
+ // of the stream threads could call RemoveStreamTask() at any time.
+ base::AutoLock autolock(stream_map_lock_);
+
+#if 0
+ // TODO(mdsteele): re-enable this code block when
+ // http://code.google.com/p/chromium/issues/detail?id=111708 is
+ // fixed.
+
+ // We already checked that stream_id > last_client_stream_id_, so there
+ // definitely shouldn't already be a stream with this ID in the map.
+ DCHECK(!stream_map_.IsStreamActive(stream_id));
+#else
+ if (stream_map_.IsStreamActive(stream_id)) {
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ return;
+ }
+#endif
+
+ // Limit the number of simultaneous open streams the client can create;
+ // refuse the stream if there are too many currently active (non-push)
+ // streams.
+ if (static_cast<int>(stream_map_.NumActiveClientStreams()) >=
+ config_->max_streams_per_connection()) {
+ SendRstStreamFrame(stream_id, net::RST_STREAM_REFUSED_STREAM);
+ return;
+ }
+
+ // Initiate a new stream.
+ last_client_stream_id_ = std::max(last_client_stream_id_, stream_id);
+ task_wrapper = new StreamTaskWrapper(
+ this, stream_id, associated_stream_id,
+ 0, // server_push_depth = 0
+ priority);
+ stream_map_.AddStreamTask(task_wrapper);
+ net::SpdySynStreamIR* frame = new net::SpdySynStreamIR(stream_id);
+ frame->set_associated_to_stream_id(associated_stream_id);
+ frame->set_priority(priority);
+ frame->set_slot(credential_slot);
+ frame->set_fin(fin);
+ frame->set_unidirectional(unidirectional);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ task_wrapper->stream()->PostInputFrame(frame);
+ }
+ DCHECK(task_wrapper);
+ // Release the lock before adding the task to the executor. This is mostly
+ // for the benefit of unit tests, for which calling AddTask will execute the
+ // task immediately (and we don't want to be holding the lock when that
+ // happens). Note that it's safe for us to pass task_wrapper here without
+ // holding the lock, because the task won't get deleted before it's been
+ // added to the executor.
+ VLOG(2) << "Received SYN_STREAM; opening stream " << stream_id;
+ executor_->AddTask(task_wrapper, priority);
+}
+
+void SpdySession::OnSynReply(net::SpdyStreamId stream_id,
+ bool fin,
+ const net::SpdyHeaderBlock& headers) {
+ // TODO(mdsteele)
+}
+
+void SpdySession::OnRstStream(net::SpdyStreamId stream_id,
+ net::SpdyRstStreamStatus status) {
+ switch (status) {
+ // These are totally benign reasons to abort a stream, so just abort the
+ // stream without a fuss.
+ case net::RST_STREAM_REFUSED_STREAM:
+ case net::RST_STREAM_CANCEL:
+ VLOG(2) << "Client cancelled/refused stream " << stream_id;
+ AbortStreamSilently(stream_id);
+ break;
+ // If there was an error, abort the stream, but log a warning first.
+ // TODO(mdsteele): Should we have special behavior for different kinds of
+ // errors?
+ default:
+ LOG(WARNING) << "Client sent RST_STREAM with "
+ << RstStreamStatusCodeToString(status)
+ << " for stream " << stream_id << ". Aborting stream.";
+ AbortStreamSilently(stream_id);
+ break;
+ }
+}
+
+void SpdySession::OnSettings(bool clear_persisted) {
+ // Do nothing; we never persist values, so we don't need to pay attention to
+ // this flag.
+}
+
+void SpdySession::OnSetting(net::SpdySettingsIds id,
+ uint8 flags, uint32 value) {
+ VLOG(4) << "Received SETTING (flags=" << flags << "): "
+ << SettingsIdToString(id) << "=" << value;
+ switch (id) {
+ case net::SETTINGS_MAX_CONCURRENT_STREAMS:
+ max_concurrent_pushes_ = value;
+ break;
+ case net::SETTINGS_INITIAL_WINDOW_SIZE:
+ // Flow control only exists for SPDY v3 and up.
+ if (spdy_version() < spdy::SPDY_VERSION_3) {
+ LOG(ERROR) << "Client sent INITIAL_WINDOW_SIZE setting over "
+ << "SPDY/" << SpdyVersionNumberString(spdy_version())
+ << ". Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ } else {
+ SetInitialWindowSize(value);
+ }
+ break;
+ case net::SETTINGS_UPLOAD_BANDWIDTH:
+ case net::SETTINGS_DOWNLOAD_BANDWIDTH:
+ case net::SETTINGS_ROUND_TRIP_TIME:
+ case net::SETTINGS_CURRENT_CWND:
+ case net::SETTINGS_DOWNLOAD_RETRANS_RATE:
+ // Ignore other settings for now.
+ break;
+ default:
+ LOG(ERROR) << "Client sent invalid SETTINGS id (" << id
+ << "). Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ break;
+ }
+}
+
+void SpdySession::OnPing(uint32 unique_id) {
+ VLOG(4) << "Received PING frame (id=" << unique_id << ")";
+ // The SPDY spec requires the server to ignore even-numbered PING frames that
+ // it did not initiate (SPDY draft 3 section 2.6.5), and right now, we never
+ // initiate pings.
+ if (unique_id % 2 == 0) {
+ return;
+ }
+
+ // Any odd-numbered PING frame we receive was initiated by the client, and
+ // should be echoed back _immediately_ (SPDY draft 2 section 2.7.6).
+ SendFrame(new net::SpdyPingIR(unique_id));
+}
+
+void SpdySession::OnGoAway(net::SpdyStreamId last_accepted_stream_id,
+ net::SpdyGoAwayStatus status) {
+ VLOG(4) << "Received GOAWAY frame (status="
+ << GoAwayStatusCodeToString(status) << ", last_accepted_stream_id="
+ << last_accepted_stream_id << ")";
+
+ // Take note that we have received a GOAWAY frame; we should not start any
+ // new server push streams on this session.
+ {
+ base::AutoLock autolock(stream_map_lock_);
+ received_goaway_ = true;
+ }
+
+ // If this was not a normal shutdown (GOAWAY_OK), we should probably log a
+ // warning to let the user know something's up.
+ switch (status) {
+ case net::GOAWAY_OK:
+ break;
+ case net::GOAWAY_PROTOCOL_ERROR:
+ LOG(WARNING) << "Client sent GOAWAY with PROTOCOL_ERROR. Possibly we "
+ << "did something wrong?";
+ break;
+ case net::GOAWAY_INTERNAL_ERROR:
+ LOG(WARNING) << "Client sent GOAWAY with INTERNAL_ERROR. Apparently "
+ << "they're broken?";
+ break;
+ default:
+ LOG(ERROR) << "Client sent GOAWAY with invalid status code ("
+ << status << "). Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ break;
+ }
+}
+
+void SpdySession::OnHeaders(net::SpdyStreamId stream_id,
+ bool fin,
+ const net::SpdyHeaderBlock& headers) {
+ // Look up the stream to post the data to. We need to lock when reading the
+ // stream map, because one of the stream threads could call
+ // RemoveStreamTask() at any time.
+ {
+ // TODO(mdsteele): This is pretty similar to the code in OnStreamFrameData.
+ // Maybe we can factor it out?
+ base::AutoLock autolock(stream_map_lock_);
+ SpdyStream* stream = stream_map_.GetStream(stream_id);
+ if (stream != NULL) {
+ VLOG(4) << "[stream " << stream_id << "] Received HEADERS frame";
+ net::SpdySynStreamIR* frame = new net::SpdySynStreamIR(stream_id);
+ frame->set_fin(true);
+ frame->GetMutableNameValueBlock()->insert(
+ headers.begin(), headers.end());
+ stream->PostInputFrame(frame);
+ return;
+ }
+ }
+
+ // Note that we release the mutex *before* sending the frame.
+ LOG(WARNING) << "Client sent HEADERS for nonexistant stream " << stream_id;
+ SendRstStreamFrame(stream_id, net::RST_STREAM_INVALID_STREAM);
+}
+
+void SpdySession::OnPushPromise(net::SpdyStreamId stream_id,
+ net::SpdyStreamId promised_stream_id) {
+ LOG(ERROR) << "Got a PUSH_PROMISE(" << stream_id << ", "
+ << promised_stream_id << ") frame from the client.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+}
+
+void SpdySession::OnWindowUpdate(net::SpdyStreamId stream_id,
+ uint32 delta_window_size) {
+ // Flow control only exists for SPDY/3 and up.
+ if (spdy_version() < spdy::SPDY_VERSION_3) {
+ LOG(ERROR) << "Got a WINDOW_UPDATE frame over SPDY/"
+ << SpdyVersionNumberString(spdy_version());
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ return;
+ }
+
+ // Stream zero is special; starting in SPDY/3.1, it represents the
+ // session-wide flow control window. For previous versions, it is invalid.
+ if (stream_id == 0) {
+ if (spdy_version() >= spdy::SPDY_VERSION_3_1) {
+ if (!shared_window_.IncreaseOutputWindowSize(delta_window_size)) {
+ LOG(ERROR) << "Got a WINDOW_UPDATE frame that overflows session "
+ << "window. Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ StopSession();
+ }
+ } else {
+ LOG(ERROR) << "Got a WINDOW_UPDATE frame for stream 0 over SPDY/"
+ << SpdyVersionNumberString(spdy_version());
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ StopSession();
+ }
+ return;
+ }
+
+ base::AutoLock autolock(stream_map_lock_);
+ SpdyStream* stream = stream_map_.GetStream(stream_id);
+ if (stream == NULL) {
+ // We must ignore WINDOW_UPDATE frames for closed streams (SPDY draft 3
+ // section 2.6.8).
+ return;
+ }
+
+ VLOG(4) << "[stream " << stream_id << "] Received WINDOW_UPDATE("
+ << delta_window_size << ") frame";
+ stream->AdjustOutputWindowSize(delta_window_size);
+}
+
+void SpdySession::SetInitialWindowSize(uint32 new_init_window_size) {
+ // Flow control only exists for SPDY v3 and up. We shouldn't be calling this
+ // method for SPDY v2.
+ if (spdy_version() < spdy::SPDY_VERSION_3) {
+ LOG(DFATAL) << "SetInitialWindowSize called for SPDY/"
+ << SpdyVersionNumberString(spdy_version());
+ return;
+ }
+
+ // Validate the new window size; it must be positive, but at most int32max.
+ if (new_init_window_size == 0 ||
+ new_init_window_size >
+ static_cast<uint32>(net::kSpdyMaximumWindowSize)) {
+ LOG(WARNING) << "Client sent invalid init window size ("
+ << new_init_window_size << "). Sending GOAWAY.";
+ SendGoAwayFrame(net::GOAWAY_PROTOCOL_ERROR);
+ return;
+ }
+ // Sanity check that our current init window size is positive. It's a signed
+ // int32, so we know it's no more than int32max.
+ DCHECK_GT(initial_window_size_, 0);
+ // We can now be sure that this subtraction won't overflow/underflow.
+ const int32 delta =
+ static_cast<int32>(new_init_window_size) - initial_window_size_;
+
+ // Set the initial window size for new streams.
+ initial_window_size_ = new_init_window_size;
+ // We also have to adjust the window size of all currently active streams by
+ // the delta (SPDY draft 3 section 2.6.8).
+ base::AutoLock autolock(stream_map_lock_);
+ stream_map_.AdjustAllOutputWindowSizes(delta);
+}
+
+// Compress (if necessary), send, and then delete the given frame object.
+void SpdySession::SendFrame(const net::SpdyFrameIR* frame_ptr) {
+ scoped_ptr<const net::SpdyFrameIR> frame(frame_ptr);
+ scoped_ptr<const net::SpdySerializedFrame> serialized_frame(
+ framer_.SerializeFrame(*frame));
+ if (serialized_frame == NULL) {
+ LOG(DFATAL) << "frame compression failed";
+ StopSession();
+ return;
+ }
+ SendFrameRaw(*serialized_frame);
+}
+
+void SpdySession::SendFrameRaw(const net::SpdySerializedFrame& frame) {
+ const SpdySessionIO::WriteStatus status = session_io_->SendFrameRaw(frame);
+ if (status == SpdySessionIO::WRITE_CONNECTION_CLOSED) {
+ // If the connection was closed and we can't write anything to the client
+ // anymore, then there's little point in continuing with the session.
+ StopSession();
+ } else {
+ DCHECK_EQ(SpdySessionIO::WRITE_SUCCESS, status);
+ }
+}
+
+void SpdySession::SendGoAwayFrame(net::SpdyGoAwayStatus status) {
+ if (!already_sent_goaway_) {
+ already_sent_goaway_ = true;
+ SendFrame(new net::SpdyGoAwayIR(last_client_stream_id_, status));
+ }
+}
+
+void SpdySession::SendRstStreamFrame(net::SpdyStreamId stream_id,
+ net::SpdyRstStreamStatus status) {
+ output_queue_.Insert(SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyRstStreamIR(stream_id, status));
+}
+
+void SpdySession::SendSettingsFrame() {
+ scoped_ptr<net::SpdySettingsIR> settings(new net::SpdySettingsIR);
+ settings->AddSetting(net::SETTINGS_MAX_CONCURRENT_STREAMS,
+ false, false, config_->max_streams_per_connection());
+ SendFrame(settings.release());
+}
+
+void SpdySession::StopSession() {
+ session_stopped_ = true;
+ // Abort all remaining streams. We need to lock when reading the stream
+ // map, because one of the stream threads could call RemoveStreamTask() at
+ // any time.
+ {
+ base::AutoLock autolock(stream_map_lock_);
+ stream_map_.AbortAllSilently();
+ }
+ shared_window_.Abort();
+ // Stop all stream threads and tasks for this SPDY session. This will
+ // block until all currently running stream tasks have exited, but since we
+ // just aborted all streams, that should hopefully happen fairly soon. Note
+ // that we must release the lock before calling this, because each stream
+ // will remove itself from the stream map as it shuts down.
+ executor_->Stop();
+}
+
+// Abort the stream without sending anything to the client.
+void SpdySession::AbortStreamSilently(net::SpdyStreamId stream_id) {
+ // We need to lock when reading the stream map, because one of the stream
+ // threads could call RemoveStreamTask() at any time.
+ base::AutoLock autolock(stream_map_lock_);
+ SpdyStream* stream = stream_map_.GetStream(stream_id);
+ if (stream != NULL) {
+ stream->AbortSilently();
+ }
+}
+
+// Send a RST_STREAM frame and then abort the stream.
+void SpdySession::AbortStream(net::SpdyStreamId stream_id,
+ net::SpdyRstStreamStatus status) {
+ SendRstStreamFrame(stream_id, status);
+ AbortStreamSilently(stream_id);
+}
+
+// Remove the StreamTaskWrapper from the stream map. This is the only method
+// of SpdySession that is ever called by another thread (specifically, it is
+// called by the StreamTaskWrapper destructor, which is called by the executor,
+// which presumably uses worker threads) -- it is because of this that we must
+// lock the stream_map_lock_ whenever we touch the stream map or its contents.
+void SpdySession::RemoveStreamTask(StreamTaskWrapper* task_wrapper) {
+ // We need to lock when touching the stream map, in case the main connection
+ // thread is currently in the middle of reading the stream map.
+ base::AutoLock autolock(stream_map_lock_);
+ VLOG(2) << "Closing stream " << task_wrapper->stream()->stream_id();
+ stream_map_.RemoveStreamTask(task_wrapper);
+}
+
+bool SpdySession::StreamMapIsEmpty() {
+ base::AutoLock autolock(stream_map_lock_);
+ return stream_map_.IsEmpty();
+}
+
+// This constructor is always called by the main connection thread, so we're
+// safe to call spdy_session_->task_factory_->NewStreamTask(). However,
+// the other methods of this class (Run(), Cancel(), and the destructor) are
+// liable to be called from other threads by the executor.
+SpdySession::StreamTaskWrapper::StreamTaskWrapper(
+ SpdySession* spdy_session,
+ net::SpdyStreamId stream_id,
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority)
+ : spdy_session_(spdy_session),
+ stream_(spdy_session->spdy_version(), stream_id, associated_stream_id,
+ server_push_depth, priority, spdy_session_->initial_window_size_,
+ &spdy_session_->output_queue_, &spdy_session_->shared_window_,
+ spdy_session_),
+ subtask_(spdy_session_->task_factory_->NewStreamTask(&stream_)) {
+ CHECK(subtask_);
+}
+
+SpdySession::StreamTaskWrapper::~StreamTaskWrapper() {
+ // Remove this object from the SpdySession's stream map.
+ spdy_session_->RemoveStreamTask(this);
+}
+
+void SpdySession::StreamTaskWrapper::Run() {
+ subtask_->CallRun();
+}
+
+void SpdySession::StreamTaskWrapper::Cancel() {
+ subtask_->CallCancel();
+}
+
+SpdySession::SpdyStreamMap::SpdyStreamMap()
+ : num_active_push_streams_(0u) {}
+
+SpdySession::SpdyStreamMap::~SpdyStreamMap() {}
+
+bool SpdySession::SpdyStreamMap::IsEmpty() {
+ DCHECK_LE(num_active_push_streams_, tasks_.size());
+ return tasks_.empty();
+}
+
+size_t SpdySession::SpdyStreamMap::NumActiveClientStreams() {
+ DCHECK_LE(num_active_push_streams_, tasks_.size());
+ return tasks_.size() - num_active_push_streams_;
+}
+
+size_t SpdySession::SpdyStreamMap::NumActivePushStreams() {
+ DCHECK_LE(num_active_push_streams_, tasks_.size());
+ return num_active_push_streams_;
+}
+
+bool SpdySession::SpdyStreamMap::IsStreamActive(net::SpdyStreamId stream_id) {
+ return tasks_.count(stream_id) > 0u;
+}
+
+void SpdySession::SpdyStreamMap::AddStreamTask(
+ StreamTaskWrapper* task_wrapper) {
+ DCHECK(task_wrapper);
+ SpdyStream* stream = task_wrapper->stream();
+ DCHECK(stream);
+ net::SpdyStreamId stream_id = stream->stream_id();
+ DCHECK_EQ(0u, tasks_.count(stream_id));
+ tasks_[stream_id] = task_wrapper;
+ if (stream->is_server_push()) {
+ ++num_active_push_streams_;
+ }
+ DCHECK_LE(num_active_push_streams_, tasks_.size());
+}
+
+void SpdySession::SpdyStreamMap::RemoveStreamTask(
+ StreamTaskWrapper* task_wrapper) {
+ DCHECK(task_wrapper);
+ SpdyStream* stream = task_wrapper->stream();
+ DCHECK(stream);
+ net::SpdyStreamId stream_id = stream->stream_id();
+ DCHECK_EQ(1u, tasks_.count(stream_id));
+ DCHECK_EQ(task_wrapper, tasks_[stream_id]);
+ if (stream->is_server_push()) {
+ DCHECK_GT(num_active_push_streams_, 0u);
+ --num_active_push_streams_;
+ }
+ tasks_.erase(stream_id);
+ DCHECK_LE(num_active_push_streams_, tasks_.size());
+}
+
+SpdyStream* SpdySession::SpdyStreamMap::GetStream(
+ net::SpdyStreamId stream_id) {
+ TaskMap::const_iterator iter = tasks_.find(stream_id);
+ if (iter == tasks_.end()) {
+ return NULL;
+ }
+ StreamTaskWrapper* task_wrapper = iter->second;
+ DCHECK(task_wrapper);
+ SpdyStream* stream = task_wrapper->stream();
+ DCHECK(stream);
+ DCHECK_EQ(stream_id, stream->stream_id());
+ return stream;
+}
+
+void SpdySession::SpdyStreamMap::AdjustAllOutputWindowSizes(int32 delta) {
+ for (TaskMap::const_iterator iter = tasks_.begin();
+ iter != tasks_.end(); ++iter) {
+ iter->second->stream()->AdjustOutputWindowSize(delta);
+ }
+}
+
+void SpdySession::SpdyStreamMap::AbortAllSilently() {
+ for (TaskMap::const_iterator iter = tasks_.begin();
+ iter != tasks_.end(); ++iter) {
+ iter->second->stream()->AbortSilently();
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SESSION_H_
+#define MOD_SPDY_COMMON_SPDY_SESSION_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/spdy_server_push_interface.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "net/instaweb/util/public/function.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class Executor;
+class SpdySessionIO;
+class SpdyServerConfig;
+class SpdyStreamTaskFactory;
+
+// Represents a SPDY session with a client. Given an Executor for processing
+// individual SPDY streams, and a SpdySessionIO for communicating with the
+// client (sending and receiving frames), this class takes care of implementing
+// the SPDY protocol and responding correctly to various situations.
+class SpdySession : public net::BufferedSpdyFramerVisitorInterface,
+ public SpdyServerPushInterface {
+ public:
+ // The SpdySession does _not_ take ownership of any of these arguments.
+ SpdySession(spdy::SpdyVersion spdy_version,
+ const SpdyServerConfig* config,
+ SpdySessionIO* session_io,
+ SpdyStreamTaskFactory* task_factory,
+ Executor* executor);
+ virtual ~SpdySession();
+
+ // What SPDY version is being used for this session?
+ spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+
+ // What are the current shared window sizes for this session? These are
+ // mostly useful for debugging. Requires that spdy_version() >=
+ // SPDY_VERSION_3_1.
+ int32 current_shared_input_window_size() const;
+ int32 current_shared_output_window_size() const;
+
+ // Process the session; don't return until the session is finished.
+ void Run();
+
+ // BufferedSpdyFramerVisitorInterface methods:
+ virtual void OnError(net::SpdyFramer::SpdyError error_code);
+ virtual void OnStreamError(
+ net::SpdyStreamId stream_id, const std::string& description);
+ virtual void OnSynStream(
+ net::SpdyStreamId stream_id, net::SpdyStreamId associated_stream_id,
+ net::SpdyPriority priority, uint8 credential_slot, bool fin,
+ bool unidirectional, const net::SpdyHeaderBlock& headers);
+ virtual void OnSynReply(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyHeaderBlock& headers);
+ virtual void OnHeaders(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyHeaderBlock& headers);
+ virtual void OnStreamFrameData(
+ net::SpdyStreamId stream_id, const char* data, size_t length, bool fin);
+ virtual void OnSettings(bool clear_persisted);
+ virtual void OnSetting(net::SpdySettingsIds id, uint8 flags, uint32 value);
+ virtual void OnPing(uint32 unique_id);
+ virtual void OnRstStream(
+ net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status);
+ virtual void OnGoAway(
+ net::SpdyStreamId last_accepted_stream_id, net::SpdyGoAwayStatus status);
+ virtual void OnWindowUpdate(
+ net::SpdyStreamId stream_id, uint32 delta_window_size);
+ virtual void OnPushPromise(
+ net::SpdyStreamId stream_id, net::SpdyStreamId promised_stream_id);
+
+ // SpdyServerPushInterface methods:
+ // Initiate a SPDY server push, roughly by pretending that the client sent a
+ // SYN_STREAM with the given headers. To repeat: the headers argument is
+ // _not_ the headers that the server will send to the client, but rather the
+ // headers to _pretend_ that the client sent to the server. Requires that
+ // spdy_version() >= SPDY/3.
+ // Note that unlike most other methods of this class, StartServerPush may be
+ // called by stream threads, not just by the connection thread.
+ virtual SpdyServerPushInterface::PushStatus StartServerPush(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers);
+
+ private:
+ // A helper class for wrapping tasks returned by
+ // SpdyStreamTaskFactory::NewStreamTask(). Running or cancelling this task
+ // simply runs/cancels the wrapped task; however, this object also keeps a
+ // SpdyStream object, and on deletion, this will remove itself from the
+ // SpdySession's list of active streams.
+ class StreamTaskWrapper : public net_instaweb::Function {
+ public:
+ // This constructor, called by the main connection thread, will call
+ // task_factory_->NewStreamTask() to produce the wrapped task.
+ StreamTaskWrapper(SpdySession* spdy_session,
+ net::SpdyStreamId stream_id,
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority);
+ virtual ~StreamTaskWrapper();
+
+ SpdyStream* stream() { return &stream_; }
+
+ protected:
+ // net_instaweb::Function methods (our implementations of these simply
+ // run/cancel the wrapped subtask):
+ virtual void Run();
+ virtual void Cancel();
+
+ private:
+ SpdySession* const spdy_session_;
+ SpdyStream stream_;
+ net_instaweb::Function* const subtask_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamTaskWrapper);
+ };
+
+ // Helper class for keeping track of active stream tasks, and separately
+ // tracking the number of active client/server-initiated streams. This class
+ // is not thread-safe without external synchronization, so it is used below
+ // along with a separate mutex.
+ class SpdyStreamMap {
+ public:
+ SpdyStreamMap();
+ ~SpdyStreamMap();
+
+ // Determine whether there are no currently active streams.
+ bool IsEmpty();
+ // Get the number of currently active streams created by the client or
+ // server, respectively.
+ size_t NumActiveClientStreams();
+ size_t NumActivePushStreams();
+ // Determine if a particular stream ID is currently active.
+ bool IsStreamActive(net::SpdyStreamId stream_id);
+ // Get the specified stream object, or NULL if the stream is inactive.
+ SpdyStream* GetStream(net::SpdyStreamId stream_id);
+ // Add a new stream. Requires that the stream ID is currently inactive.
+ void AddStreamTask(StreamTaskWrapper* task);
+ // Remove a stream task. Requires that the stream is currently active.
+ void RemoveStreamTask(StreamTaskWrapper* task);
+ // Adjust the output window size of all active streams by the same delta.
+ void AdjustAllOutputWindowSizes(int32 delta);
+ // Abort all streams in the map. Note that this won't immediately empty
+ // the map (the tasks still have to shut down).
+ void AbortAllSilently();
+
+ private:
+ typedef std::map<net::SpdyStreamId, StreamTaskWrapper*> TaskMap;
+ TaskMap tasks_;
+ size_t num_active_push_streams_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyStreamMap);
+ };
+
+ // Validate and set the per-stream initial flow-control window size to the
+ // new value. Must be using SPDY v3 or later to call this method.
+ void SetInitialWindowSize(uint32 new_init_window_size);
+
+ // Send a single SPDY frame to the client, compressing it first if necessary.
+ // Stop the session if the connection turns out to be closed. This method
+ // takes ownership of the passed frame and will delete it.
+ void SendFrame(const net::SpdyFrameIR* frame);
+ // Send the frame as-is (without taking ownership). Stop the session if the
+ // connection turns out to be closed.
+ void SendFrameRaw(const net::SpdySerializedFrame& frame);
+
+ // Immediately send a GOAWAY frame to the client with the given status,
+ // unless we've already sent one. This also prevents us from creating any
+ // new streams, so calling this is the best way to shut the session down
+ // gracefully; once all streams have finished normally and no new ones can be
+ // created, the session will shut itself down.
+ void SendGoAwayFrame(net::SpdyGoAwayStatus status);
+ // Enqueue a RST_STREAM frame for the given stream ID. Note that this does
+ // not abort the stream if it exists; for that, use AbortStream().
+ void SendRstStreamFrame(net::SpdyStreamId stream_id,
+ net::SpdyRstStreamStatus status);
+ // Immediately send our SETTINGS frame, with values based on our
+ // SpdyServerConfig object. This should be done exactly once, at session
+ // start.
+ void SendSettingsFrame();
+
+ // Close down the whole session immediately. Abort all active streams, and
+ // then block until all stream threads have shut down.
+ void StopSession();
+ // Abort the stream without sending anything to the client.
+ void AbortStreamSilently(net::SpdyStreamId stream_id);
+ // Send a RST_STREAM frame and then abort the stream.
+ void AbortStream(net::SpdyStreamId stream_id,
+ net::SpdyRstStreamStatus status);
+
+ // Remove the given StreamTaskWrapper object from the stream map. This is
+ // the only other method of this class, aside from StartServerPush, that
+ // might be called from another thread. (Specifically, it is called by the
+ // StreamTaskWrapper destructor, which is called by the executor).
+ void RemoveStreamTask(StreamTaskWrapper* stream_data);
+
+ // Grab the stream_map_lock_ and check if stream_map_ is empty.
+ bool StreamMapIsEmpty();
+
+ // These fields are accessed only by the main connection thread, so they need
+ // not be protected by a lock:
+ const spdy::SpdyVersion spdy_version_;
+ const SpdyServerConfig* const config_;
+ SpdySessionIO* const session_io_;
+ SpdyStreamTaskFactory* const task_factory_;
+ Executor* const executor_;
+ net::BufferedSpdyFramer framer_;
+ bool session_stopped_; // StopSession() has been called
+ bool already_sent_goaway_; // GOAWAY frame has been sent
+ net::SpdyStreamId last_client_stream_id_;
+ int32 initial_window_size_; // per-stream initial flow-control window size
+ uint32 max_concurrent_pushes_; // max number of active server pushes at once
+
+ // The stream map must be protected by a lock, because each stream thread
+ // will remove itself from the map (by calling RemoveStreamTask) when the
+ // stream closes. You MUST hold the lock to use the stream_map_ OR to use
+ // any of the StreamTaskWrapper or SpdyStream objects contained therein
+ // (e.g. to post a frame to the stream), otherwise the stream object may be
+ // deleted by another thread while you're using it. You should NOT be
+ // holding the lock when you e.g. send a frame to the client, as that may
+ // block for a long time.
+ base::Lock stream_map_lock_;
+ SpdyStreamMap stream_map_;
+ // These fields are also protected by the stream_map_lock_; they are used for
+ // controlling server pushes, which can be initiated by stream threads as
+ // well as by the connection thread. We could use a separate lock for these,
+ // but right now we probably don't need that much locking granularity.
+ net::SpdyStreamId last_server_push_stream_id_;
+ bool received_goaway_; // we've received a GOAWAY frame from the client
+
+ // These objects are also shared between all stream threads, but these
+ // classes are each thread-safe, and don't need additional synchronization.
+ SpdyFramePriorityQueue output_queue_;
+ SharedFlowControlWindow shared_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySession);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_SESSION_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_session_io.h"
+
+namespace mod_spdy {
+
+SpdySessionIO::SpdySessionIO() {}
+
+SpdySessionIO::~SpdySessionIO() {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SESSION_IO_H_
+#define MOD_SPDY_COMMON_SPDY_SESSION_IO_H_
+
+#include "base/basictypes.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+class BufferedSpdyFramer;
+} // namespace net
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// SpdySessionIO is a helper interface for the SpdySession class. The
+// SpdySessionIO takes care of implementation-specific details about how to
+// send and receive data, allowing the SpdySession to focus on the SPDY
+// protocol itself. For example, a SpdySessionIO for Apache would hold onto a
+// conn_rec object and invoke the input and output filter chains for
+// ProcessAvailableInput and SendFrameRaw, respectively. The SpdySessionIO
+// itself does not need to be thread-safe -- it is only ever used by the main
+// connection thread.
+class SpdySessionIO {
+ public:
+ // Status to describe whether reading succeeded.
+ enum ReadStatus {
+ READ_SUCCESS, // we successfully pushed data into the SpdyFramer
+ READ_NO_DATA, // no data is currently available
+ READ_CONNECTION_CLOSED, // the connection has been closed
+ READ_ERROR // an unrecoverable error (e.g. client sent malformed data)
+ };
+
+ // Status to describe whether writing succeeded.
+ enum WriteStatus {
+ WRITE_SUCCESS, // we successfully wrote the frame out to the network
+ WRITE_CONNECTION_CLOSED, // the connection has been closed
+ };
+
+ SpdySessionIO();
+ virtual ~SpdySessionIO();
+
+ // Return true if the connection has been externally aborted and should
+ // stop, false otherwise.
+ virtual bool IsConnectionAborted() = 0;
+
+ // Pull any available input data from the connection and feed it into the
+ // ProcessInput() method of the given SpdyFramer. If no input data is
+ // currently available and the block argument is true, this should block
+ // until more data arrives; otherwise, this should not block.
+ virtual ReadStatus ProcessAvailableInput(
+ bool block, net::BufferedSpdyFramer* framer) = 0;
+
+ // Send a single SPDY frame to the client as-is; block until it has been
+ // sent down the wire. Return true on success.
+ //
+ // TODO(mdsteele): We do need to be able to flush a single frame down the
+ // wire, but we probably don't need/want to flush every single frame
+ // individually in places where we send multiple frames at once. We'll
+ // probably want to adjust this API a bit.
+ virtual WriteStatus SendFrameRaw(const net::SpdySerializedFrame& frame) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionIO);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_SESSION_IO_H_
--- /dev/null
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/spdy_session.h"
+
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_session_io.h"
+#include "mod_spdy/common/spdy_stream_task_factory.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "mod_spdy/common/thread_pool.h"
+#include "net/instaweb/util/public/function.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using mod_spdy::testing::IsDataFrame;
+using mod_spdy::testing::IsGoAway;
+using mod_spdy::testing::IsHeaders;
+using mod_spdy::testing::IsPing;
+using mod_spdy::testing::IsRstStream;
+using mod_spdy::testing::IsSettings;
+using mod_spdy::testing::IsSynReply;
+using mod_spdy::testing::IsSynStream;
+using testing::_;
+using testing::AllOf;
+using testing::AtLeast;
+using testing::DoAll;
+using testing::Eq;
+using testing::Invoke;
+using testing::InvokeWithoutArgs;
+using testing::NotNull;
+using testing::Property;
+using testing::Return;
+using testing::StrictMock;
+using testing::WithArg;
+
+namespace {
+
+void AddRequestHeaders(mod_spdy::spdy::SpdyVersion version,
+ net::SpdyNameValueBlock *headers) {
+ const bool spdy2 = version < mod_spdy::spdy::SPDY_VERSION_3;
+ (*headers)[spdy2 ? mod_spdy::http::kHost :
+ mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ (*headers)[spdy2 ? mod_spdy::spdy::kSpdy2Method :
+ mod_spdy::spdy::kSpdy3Method] = "GET";
+ (*headers)[spdy2 ? mod_spdy::spdy::kSpdy2Scheme :
+ mod_spdy::spdy::kSpdy3Scheme] = "https";
+ (*headers)[spdy2 ? mod_spdy::spdy::kSpdy2Url :
+ mod_spdy::spdy::kSpdy3Path] = "/foo/index.html";
+ (*headers)[spdy2 ? mod_spdy::spdy::kSpdy2Version :
+ mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+}
+
+void AddResponseHeaders(mod_spdy::spdy::SpdyVersion version,
+ net::SpdyNameValueBlock *headers) {
+ const bool spdy2 = version < mod_spdy::spdy::SPDY_VERSION_3;
+ (*headers)[spdy2 ? mod_spdy::spdy::kSpdy2Status :
+ mod_spdy::spdy::kSpdy3Status] = "200";
+ (*headers)[spdy2 ? mod_spdy::spdy::kSpdy2Version :
+ mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ (*headers)[mod_spdy::http::kContentType] = "text/html";
+}
+
+void AddInitialServerPushHeaders(const std::string& path,
+ net::SpdyNameValueBlock *headers) {
+ (*headers)[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ (*headers)[mod_spdy::spdy::kSpdy3Path] = path;
+ (*headers)[mod_spdy::spdy::kSpdy3Scheme] = "https";
+}
+
+class MockSpdySessionIO : public mod_spdy::SpdySessionIO {
+ public:
+ MOCK_METHOD0(IsConnectionAborted, bool());
+ MOCK_METHOD2(ProcessAvailableInput,
+ ReadStatus(bool, net::BufferedSpdyFramer*));
+ MOCK_METHOD1(SendFrameRaw, WriteStatus(const net::SpdySerializedFrame&));
+};
+
+class MockSpdyStreamTaskFactory : public mod_spdy::SpdyStreamTaskFactory {
+ public:
+ MOCK_METHOD1(NewStreamTask, net_instaweb::Function*(mod_spdy::SpdyStream*));
+};
+
+class MockStreamTask : public net_instaweb::Function {
+ public:
+ MockStreamTask() : stream(NULL) {}
+ MOCK_METHOD0(Run, void());
+ MOCK_METHOD0(Cancel, void());
+ mod_spdy::SpdyStream* stream;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockStreamTask);
+};
+
+// gMock action to be used with NewStreamTask.
+ACTION_P(ReturnMockTask, task) {
+ task->stream = arg0;
+ return task;
+}
+
+// gMock action to be used with MockStreamTask::Run.
+ACTION_P4(StartServerPush, task, priority, path, expected_status) {
+ net::SpdyHeaderBlock push_headers;
+ AddInitialServerPushHeaders(path, &push_headers);
+ EXPECT_EQ(expected_status,
+ task->stream->StartServerPush(priority, push_headers));
+}
+
+// gMock action to be used with MockStreamTask::Run.
+ACTION_P(SendResponseHeaders, task) {
+ net::SpdyHeaderBlock headers;
+ AddResponseHeaders(task->stream->spdy_version(), &headers);
+ if (task->stream->is_server_push()) {
+ task->stream->SendOutputHeaders(headers, false);
+ } else {
+ task->stream->SendOutputSynReply(headers, false);
+ }
+}
+
+// gMock action to be used with MockStreamTask::Run.
+ACTION_P3(SendDataFrame, task, data, fin) {
+ task->stream->SendOutputDataFrame(data, fin);
+}
+
+// gMock action to be used with MockStreamTask::Run.
+ACTION_P(ConsumeInputUntilAborted, task) {
+ while (!task->stream->is_aborted()) {
+ net::SpdyFrameIR* raw_frame = NULL;
+ if (task->stream->GetInputFrame(true, &raw_frame)) {
+ delete raw_frame;
+ }
+ }
+}
+
+// An executor that runs all tasks in the same thread, either immediately when
+// they are added or when it is told to run them.
+class InlineExecutor : public mod_spdy::Executor {
+ public:
+ InlineExecutor() : run_on_add_(false), stopped_(false) {}
+ virtual ~InlineExecutor() { Stop(); }
+
+ virtual void AddTask(net_instaweb::Function* task,
+ net::SpdyPriority priority) {
+ if (stopped_) {
+ task->CallCancel();
+ } else if (run_on_add_) {
+ task->CallRun();
+ } else {
+ tasks_.push_back(task);
+ }
+ }
+ virtual void Stop() {
+ stopped_ = true;
+ while (!tasks_.empty()) {
+ tasks_.front()->CallCancel();
+ tasks_.pop_front();
+ }
+ }
+ void RunOne() {
+ if (!tasks_.empty()) {
+ tasks_.front()->CallRun();
+ tasks_.pop_front();
+ }
+ }
+ void RunAll() {
+ while (!tasks_.empty()) {
+ RunOne();
+ }
+ }
+ void set_run_on_add(bool run) { run_on_add_ = run; }
+ bool stopped() const { return stopped_; }
+
+ private:
+ std::list<net_instaweb::Function*> tasks_;
+ bool run_on_add_;
+ bool stopped_;
+
+ DISALLOW_COPY_AND_ASSIGN(InlineExecutor);
+};
+
+// A BufferedSpdyFramer visitor that constructs IR objects for the frames it
+// parses.
+class ClientVisitor : public net::BufferedSpdyFramerVisitorInterface {
+ public:
+ ClientVisitor() : last_data_(NULL), last_settings_(NULL) {}
+ virtual ~ClientVisitor() {}
+
+ virtual void OnError(net::SpdyFramer::SpdyError error_code) {}
+ virtual void OnStreamError(net::SpdyStreamId stream_id,
+ const std::string& description) {}
+ virtual void OnSynStream(net::SpdyStreamId id, net::SpdyStreamId assoc_id,
+ net::SpdyPriority priority, uint8 slot,
+ bool fin, bool unidirectional,
+ const net::SpdyHeaderBlock& headers) {
+ scoped_ptr<net::SpdySynStreamIR> frame(new net::SpdySynStreamIR(id));
+ frame->set_associated_to_stream_id(assoc_id);
+ frame->set_priority(priority);
+ frame->set_slot(slot);
+ frame->set_fin(fin);
+ frame->set_unidirectional(unidirectional);
+ frame->GetMutableNameValueBlock()->insert(
+ headers.begin(), headers.end());
+ last_frame_.reset(frame.release());
+ }
+ virtual void OnSynReply(net::SpdyStreamId id, bool fin,
+ const net::SpdyHeaderBlock& headers) {
+ scoped_ptr<net::SpdySynReplyIR> frame(new net::SpdySynReplyIR(id));
+ frame->set_fin(fin);
+ frame->GetMutableNameValueBlock()->insert(
+ headers.begin(), headers.end());
+ last_frame_.reset(frame.release());
+ }
+ virtual void OnHeaders(net::SpdyStreamId id, bool fin,
+ const net::SpdyHeaderBlock& headers) {
+ scoped_ptr<net::SpdyHeadersIR> frame(new net::SpdyHeadersIR(id));
+ frame->set_fin(fin);
+ frame->GetMutableNameValueBlock()->insert(
+ headers.begin(), headers.end());
+ last_frame_.reset(frame.release());
+ }
+ virtual void OnStreamFrameData(net::SpdyStreamId id, const char* data,
+ size_t len, bool fin) {
+ if (len == 0 && last_data_ != NULL && last_data_ == last_frame_.get()) {
+ last_data_->set_fin(fin);
+ } else {
+ scoped_ptr<net::SpdyDataIR> frame(new net::SpdyDataIR(
+ id, base::StringPiece(data, len)));
+ frame->set_fin(fin);
+ last_data_ = frame.get();
+ last_frame_.reset(frame.release());
+ }
+ }
+ virtual void OnSettings(bool clear_persisted) {
+ scoped_ptr<net::SpdySettingsIR> frame(new net::SpdySettingsIR);
+ frame->set_clear_settings(clear_persisted);
+ last_settings_ = frame.get();
+ last_frame_.reset(frame.release());
+ }
+ virtual void OnSetting(net::SpdySettingsIds id, uint8 flags, uint32 value) {
+ CHECK(last_settings_ != NULL && last_settings_ == last_frame_.get());
+ last_settings_->AddSetting(
+ id, (flags & net::SETTINGS_FLAG_PLEASE_PERSIST),
+ (flags & net::SETTINGS_FLAG_PERSISTED), value);
+ }
+ virtual void OnPing(uint32 id) {
+ last_frame_.reset(new net::SpdyPingIR(id));
+ }
+ virtual void OnRstStream(net::SpdyStreamId id,
+ net::SpdyRstStreamStatus status) {
+ last_frame_.reset(new net::SpdyRstStreamIR(id, status));
+ }
+ virtual void OnGoAway(net::SpdyStreamId id, net::SpdyGoAwayStatus status) {
+ last_frame_.reset(new net::SpdyGoAwayIR(id, status));
+ }
+ virtual void OnWindowUpdate(net::SpdyStreamId id, uint32 delta) {
+ last_frame_.reset(new net::SpdyWindowUpdateIR(id, delta));
+ }
+ virtual void OnPushPromise(net::SpdyStreamId id, net::SpdyStreamId promise) {
+ last_frame_.reset(new net::SpdyPushPromiseIR(id, promise));
+ }
+
+ net::SpdyFrameIR* ReleaseLastFrame() {
+ return last_frame_.release();
+ }
+
+ private:
+ net::SpdyDataIR* last_data_;
+ net::SpdySettingsIR* last_settings_;
+ scoped_ptr<net::SpdyFrameIR> last_frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientVisitor);
+};
+
+ACTION_P2(ClientDecodeFrame, test, matcher) {
+ scoped_ptr<net::SpdyFrameIR> frame(test->DecodeFrameOnClient(arg0));
+ ASSERT_TRUE(frame != NULL);
+ EXPECT_THAT(*frame, matcher);
+}
+
+ACTION_P3(SendBackWindowUpdate, test, stream_id, delta) {
+ test->ReceiveWindowUpdateFrameFromClient(stream_id, delta);
+}
+
+ACTION_P3(SendBackSettings, test, key, value) {
+ test->ReceiveSettingsFrameFromClient(key, value);
+}
+
+// Base class for SpdySession tests.
+class SpdySessionTestBase :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ SpdySessionTestBase()
+ : spdy_version_(GetParam()),
+ client_framer_(mod_spdy::SpdyVersionToFramerVersion(spdy_version_),
+ true) {
+ client_framer_.set_visitor(&client_visitor_);
+ ON_CALL(session_io_, IsConnectionAborted()).WillByDefault(Return(false));
+ ON_CALL(session_io_, ProcessAvailableInput(_, NotNull()))
+ .WillByDefault(Invoke(this, &SpdySessionTestBase::ReadNextInputChunk));
+ ON_CALL(session_io_, SendFrameRaw(_))
+ .WillByDefault(Return(mod_spdy::SpdySessionIO::WRITE_SUCCESS));
+ }
+
+ // Use as gMock action for ProcessAvailableInput:
+ // Invoke(this, &SpdySessionTest::ReadNextInputChunk)
+ mod_spdy::SpdySessionIO::ReadStatus ReadNextInputChunk(
+ bool block, net::BufferedSpdyFramer* framer) {
+ if (input_queue_.empty()) {
+ return mod_spdy::SpdySessionIO::READ_NO_DATA;
+ }
+ const std::string chunk = input_queue_.front();
+ input_queue_.pop_front();
+ framer->ProcessInput(chunk.data(), chunk.size());
+ return (framer->HasError() ? mod_spdy::SpdySessionIO::READ_ERROR :
+ mod_spdy::SpdySessionIO::READ_SUCCESS);
+ }
+
+ // This is called by the ClientDecodeFrame gMock action defined above.
+ net::SpdyFrameIR* DecodeFrameOnClient(
+ const net::SpdySerializedFrame& frame) {
+ client_framer_.ProcessInput(frame.data(), frame.size());
+ return client_visitor_.ReleaseLastFrame();
+ }
+
+ // Push a frame into the input queue.
+ void ReceiveFrameFromClient(const net::SpdySerializedFrame& frame) {
+ input_queue_.push_back(std::string(frame.data(), frame.size()));
+ }
+
+ // Push a PING frame into the input queue.
+ void ReceivePingFromClient(uint32 id) {
+ scoped_ptr<net::SpdySerializedFrame> frame(
+ client_framer_.CreatePingFrame(id));
+ ReceiveFrameFromClient(*frame);
+ }
+
+ // Push a valid SYN_STREAM frame into the input queue.
+ void ReceiveSynStreamFromClient(net::SpdyStreamId stream_id,
+ net::SpdyPriority priority,
+ net::SpdyControlFlags flags) {
+ net::SpdyHeaderBlock headers;
+ AddRequestHeaders(spdy_version_, &headers);
+ scoped_ptr<net::SpdySerializedFrame> frame(client_framer_.CreateSynStream(
+ stream_id, 0, priority, 0, flags,
+ true, // true = use compression
+ &headers));
+ ReceiveFrameFromClient(*frame);
+ }
+
+ // Push a valid DATA frame into the input queue.
+ void ReceiveDataFromClient(net::SpdyStreamId stream_id,
+ base::StringPiece data,
+ net::SpdyDataFlags flags) {
+ scoped_ptr<net::SpdySerializedFrame> frame(client_framer_.CreateDataFrame(
+ stream_id, data.data(), data.size(), flags));
+ ReceiveFrameFromClient(*frame);
+ }
+
+ // Push a SETTINGS frame into the input queue.
+ void ReceiveSettingsFrameFromClient(
+ net::SpdySettingsIds setting, uint32 value) {
+ net::SettingsMap settings;
+ settings[setting] = std::make_pair(net::SETTINGS_FLAG_NONE, value);
+ scoped_ptr<net::SpdySerializedFrame> frame(
+ client_framer_.CreateSettings(settings));
+ ReceiveFrameFromClient(*frame);
+ }
+
+ // Push a WINDOW_UPDATE frame into the input queue.
+ void ReceiveWindowUpdateFrameFromClient(
+ net::SpdyStreamId stream_id, uint32 delta) {
+ scoped_ptr<net::SpdySerializedFrame> frame(
+ client_framer_.CreateWindowUpdate(stream_id, delta));
+ ReceiveFrameFromClient(*frame);
+ }
+
+ protected:
+ void ExpectSendFrame(::testing::Matcher<const net::SpdyFrameIR&> matcher) {
+ EXPECT_CALL(session_io_, SendFrameRaw(_))
+ .WillOnce(DoAll(ClientDecodeFrame(this, matcher),
+ Return(mod_spdy::SpdySessionIO::WRITE_SUCCESS)));
+ }
+
+ void ExpectBeginServerPush(
+ net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+ net::SpdyPriority priority, const std::string& path) {
+ net::SpdyNameValueBlock headers;
+ AddInitialServerPushHeaders(path, &headers);
+ ExpectSendFrame(IsSynStream(stream_id, assoc_stream_id, priority, false,
+ true, headers));
+ }
+
+ void ExpectSendSynReply(net::SpdyStreamId stream_id, bool fin) {
+ net::SpdyNameValueBlock headers;
+ AddResponseHeaders(spdy_version_, &headers);
+ ExpectSendFrame(IsSynReply(stream_id, fin, headers));
+ }
+
+ void ExpectSendHeaders(net::SpdyStreamId stream_id, bool fin) {
+ net::SpdyNameValueBlock headers;
+ AddResponseHeaders(spdy_version_, &headers);
+ ExpectSendFrame(IsHeaders(stream_id, fin, headers));
+ }
+
+ void ExpectSendGoAway(net::SpdyStreamId last_stream_id,
+ net::SpdyGoAwayStatus status) {
+ // SPDY/2 doesn't have status codes on GOAWAY frames, so for SPDY/2 the
+ // client framer decodes it as GOAWAY_OK regardless of what we sent.
+ if (spdy_version_ == mod_spdy::spdy::SPDY_VERSION_2) {
+ ExpectSendFrame(IsGoAway(last_stream_id, net::GOAWAY_OK));
+ } else {
+ ExpectSendFrame(IsGoAway(last_stream_id, status));
+ }
+ }
+
+ const mod_spdy::spdy::SpdyVersion spdy_version_;
+ ClientVisitor client_visitor_;
+ net::BufferedSpdyFramer client_framer_;
+ mod_spdy::SpdyServerConfig config_;
+ StrictMock<MockSpdySessionIO> session_io_;
+ StrictMock<MockSpdyStreamTaskFactory> task_factory_;
+ std::list<std::string> input_queue_;
+};
+
+// Class for most SpdySession tests; this uses an InlineExecutor, so that test
+// behavior is very predictable.
+class SpdySessionTest : public SpdySessionTestBase {
+ public:
+ SpdySessionTest()
+ : session_(spdy_version_, &config_, &session_io_, &task_factory_,
+ &executor_) {}
+
+ protected:
+ InlineExecutor executor_;
+ mod_spdy::SpdySession session_;
+};
+
+// Test that if the connection is already closed, we stop immediately.
+TEST_P(SpdySessionTest, ConnectionAlreadyClosed) {
+ testing::InSequence seq;
+ EXPECT_CALL(session_io_, SendFrameRaw(_))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::WRITE_CONNECTION_CLOSED));
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Test that when the connection is aborted, we stop.
+TEST_P(SpdySessionTest, ImmediateConnectionAbort) {
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted()).WillOnce(Return(true));
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Test responding to a PING frame from the client (followed by the connection
+// closing, so that we can exit the Run loop).
+TEST_P(SpdySessionTest, SinglePing) {
+ ReceivePingFromClient(47);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendFrame(IsPing(47));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::READ_CONNECTION_CLOSED));
+ ExpectSendGoAway(0, net::GOAWAY_OK);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Test handling a single stream request.
+TEST_P(SpdySessionTest, SingleStream) {
+ MockStreamTask* task = new MockStreamTask;
+ executor_.set_run_on_add(false);
+ const net::SpdyStreamId stream_id = 1;
+ const net::SpdyPriority priority = 2;
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task));
+ EXPECT_CALL(session_io_, IsConnectionAborted())
+ .WillOnce(DoAll(InvokeWithoutArgs(&executor_, &InlineExecutor::RunAll),
+ Return(false)));
+ EXPECT_CALL(*task, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task), SendDataFrame(task, "foobar", false),
+ SendDataFrame(task, "quux", true)));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(false), NotNull()));
+ ExpectSendSynReply(stream_id, false);
+ ExpectSendFrame(IsDataFrame(stream_id, false, "foobar"));
+ ExpectSendFrame(IsDataFrame(stream_id, true, "quux"));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::READ_CONNECTION_CLOSED));
+ ExpectSendGoAway(1, net::GOAWAY_OK);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Test that if SendFrameRaw fails, we immediately stop trying to send data and
+// shut down the session.
+TEST_P(SpdySessionTest, ShutDownSessionIfSendFrameRawFails) {
+ MockStreamTask* task = new MockStreamTask;
+ executor_.set_run_on_add(false);
+ const net::SpdyStreamId stream_id = 1;
+ const net::SpdyPriority priority = 2;
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ testing::InSequence seq;
+ // We start out the same way as in the SingleStream test above.
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, _));
+ EXPECT_CALL(task_factory_, NewStreamTask(_))
+ .WillOnce(ReturnMockTask(task));
+ EXPECT_CALL(session_io_, IsConnectionAborted())
+ .WillOnce(DoAll(InvokeWithoutArgs(&executor_, &InlineExecutor::RunAll),
+ Return(false)));
+ EXPECT_CALL(*task, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task), SendDataFrame(task, "foobar", false),
+ SendDataFrame(task, "quux", true)));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, _));
+ ExpectSendSynReply(stream_id, false);
+ // At this point, the connection is closed by the client.
+ EXPECT_CALL(session_io_, SendFrameRaw(_))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::WRITE_CONNECTION_CLOSED));
+ // Even though we have another frame to send at this point (already in the
+ // output queue), we immediately stop sending data and exit the session.
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Test that when the client sends us garbage data, we send a GOAWAY frame and
+// then quit.
+TEST_P(SpdySessionTest, SendGoawayInResponseToGarbage) {
+ input_queue_.push_back("\x88\x5f\x92\x02\xf8\x92\x12\xd1"
+ "\x82\xdc\x1a\x40\xbb\xb2\x9d\x13");
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Test that when the client sends us a SYN_STREAM with a corrupted header
+// block, we send a GOAWAY frame and then quit.
+TEST_P(SpdySessionTest, SendGoawayForBadSynStreamCompression) {
+ net::SpdyHeaderBlock headers;
+ headers["foobar"] = "Foo is to bar as bar is to baz.";
+ net::SpdyFramer framer(mod_spdy::SpdyVersionToFramerVersion(spdy_version_));
+ framer.set_enable_compression(false);
+ scoped_ptr<net::SpdySerializedFrame> frame(framer.CreateSynStream(
+ 1, 0, framer.GetHighestPriority(), 0, net::CONTROL_FLAG_FIN,
+ false, // false = no compression
+ &headers));
+ ReceiveFrameFromClient(*frame);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// TODO(mdsteele): At the moment, SpdyFramer DCHECKs that the stream ID is
+// nonzero when decoding, so this test would crash in debug builds. Once this
+// has been corrected in the Chromium code, we can remove this #ifdef.
+#ifdef NDEBUG
+// Test that when the client sends us a SYN_STREAM with a stream ID of 0, we
+// send a GOAWAY frame and then quit.
+TEST_P(SpdySessionTest, SendGoawayForSynStreamIdZero) {
+ net::SpdyHeaderBlock headers;
+ AddRequestHeaders(spdy_version_, &headers);
+ scoped_ptr<net::SpdySerializedFrame> frame(client_framer_.CreateSynStream(
+ 0, 0, client_framer_.GetHighestPriority(), 0, net::CONTROL_FLAG_FIN,
+ true, // true = use compression
+ &headers));
+ ReceiveFrameFromClient(*frame);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+#endif
+
+// Test that when the client sends us two SYN_STREAMs with the same ID, we send
+// a GOAWAY frame (but still finish out the good stream before quitting).
+TEST_P(SpdySessionTest, SendGoawayForDuplicateStreamId) {
+ MockStreamTask* task = new MockStreamTask;
+ executor_.set_run_on_add(false);
+ const net::SpdyStreamId stream_id = 1;
+ const net::SpdyPriority priority = 2;
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ // Get the first SYN_STREAM; it looks good, so create a new task (but because
+ // we set executor_.set_run_on_add(false) above, it doesn't execute yet).
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ // There's an active stream out, so ProcessAvailableInput should have false
+ // for the first argument (false = nonblocking read). Here we get the second
+ // SYN_STREAM with the same stream ID, so we should send GOAWAY.
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(false), NotNull()));
+ ExpectSendGoAway(1, net::GOAWAY_PROTOCOL_ERROR);
+ // At this point, tell the executor to run the task.
+ EXPECT_CALL(session_io_, IsConnectionAborted())
+ .WillOnce(DoAll(InvokeWithoutArgs(&executor_, &InlineExecutor::RunAll),
+ Return(false)));
+ EXPECT_CALL(*task, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task), SendDataFrame(task, "foobar", false),
+ SendDataFrame(task, "quux", true)));
+ // The stream is no longer active, but there are pending frames to send, so
+ // we shouldn't block on input.
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(false), NotNull()));
+ // Now we should send the output.
+ ExpectSendSynReply(stream_id, false);
+ ExpectSendFrame(IsDataFrame(stream_id, false, "foobar"));
+ ExpectSendFrame(IsDataFrame(stream_id, true, "quux"));
+ // Finally, there is no more output to send, and no chance of creating new
+ // streams (since we GOAWAY'd), so we quit.
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, SpdySessionTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+// Create a type alias so that we can instantiate some of our
+// SpdySessionTest-based tests using a different set of parameters.
+typedef SpdySessionTest SpdySessionNoFlowControlTest;
+
+// Test that we send GOAWAY if the client tries to send
+// SETTINGS_INITIAL_WINDOW_SIZE over SPDY v2.
+TEST_P(SpdySessionNoFlowControlTest, SendGoawayForInitialWindowSize) {
+ net::SettingsMap settings;
+ settings[net::SETTINGS_INITIAL_WINDOW_SIZE] =
+ std::make_pair(net::SETTINGS_FLAG_NONE, 4000);
+ scoped_ptr<net::SpdySerializedFrame> frame(
+ client_framer_.CreateSettings(settings));
+ ReceiveFrameFromClient(*frame);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Only run no-flow-control tests for SPDY v2.
+INSTANTIATE_TEST_CASE_P(Spdy2, SpdySessionNoFlowControlTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2));
+
+// Test class for flow-control tests. This uses a ThreadPool Executor, so that
+// we can test concurrency behavior.
+class SpdySessionFlowControlTest : public SpdySessionTestBase {
+ public:
+ SpdySessionFlowControlTest() : thread_pool_(1, 1) {}
+
+ void SetUp() {
+ ASSERT_TRUE(thread_pool_.Start());
+ executor_.reset(thread_pool_.NewExecutor());
+ session_.reset(new mod_spdy::SpdySession(
+ spdy_version_, &config_, &session_io_, &task_factory_,
+ executor_.get()));
+ }
+
+ void ExpectSendDataGetWindowUpdateBack(
+ net::SpdyStreamId stream_id, bool fin, base::StringPiece payload) {
+ EXPECT_CALL(session_io_, SendFrameRaw(_)).WillOnce(DoAll(
+ ClientDecodeFrame(this, IsDataFrame(stream_id, fin, payload)),
+ SendBackWindowUpdate(this, stream_id, payload.size()),
+ Return(mod_spdy::SpdySessionIO::WRITE_SUCCESS)));
+ }
+
+ protected:
+ mod_spdy::ThreadPool thread_pool_;
+ scoped_ptr<mod_spdy::Executor> executor_;
+ scoped_ptr<mod_spdy::SpdySession> session_;
+};
+
+TEST_P(SpdySessionFlowControlTest, SingleStreamWithFlowControl) {
+ MockStreamTask* task = new MockStreamTask;
+ // Start by setting the initial window size to very small (three bytes).
+ ReceiveSettingsFrameFromClient(net::SETTINGS_INITIAL_WINDOW_SIZE, 3);
+ // Then send a SYN_STREAM.
+ const net::SpdyStreamId stream_id = 1;
+ const net::SpdyPriority priority = 2;
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ // We'll have to go through the loop at least five times -- once for each of
+ // five frames that we _must_ receive (SETTINGS, SYN_STREAM, and three
+ // WINDOW_UDPATEs.
+ EXPECT_CALL(session_io_, IsConnectionAborted()).Times(AtLeast(5));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()))
+ .Times(AtLeast(5));
+
+ // The rest of these will have to happen in a fixed order.
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task));
+ EXPECT_CALL(*task, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task), SendDataFrame(task, "foobar", false),
+ SendDataFrame(task, "quux", true)));
+ // Since the window size is just three bytes, we can only send three bytes at
+ // a time.
+ ExpectSendSynReply(stream_id, false);
+ ExpectSendDataGetWindowUpdateBack(stream_id, false, "foo");
+ ExpectSendDataGetWindowUpdateBack(stream_id, false, "bar");
+ ExpectSendDataGetWindowUpdateBack(stream_id, false, "quu");
+ ExpectSendDataGetWindowUpdateBack(stream_id, true, "x");
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::READ_CONNECTION_CLOSED));
+ ExpectSendGoAway(stream_id, net::GOAWAY_OK);
+
+ session_->Run();
+}
+
+// Suppose the input side of the connection closes while we're blocked on flow
+// control; we should abort the blocked streams.
+TEST_P(SpdySessionFlowControlTest, CeaseInputWithFlowControl) {
+ MockStreamTask* task = new MockStreamTask;
+ // Start by setting the initial window size to very small (three bytes).
+ ReceiveSettingsFrameFromClient(net::SETTINGS_INITIAL_WINDOW_SIZE, 3);
+ // Then send a SYN_STREAM.
+ const net::SpdyStreamId stream_id = 1;
+ const net::SpdyPriority priority = 2;
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ EXPECT_CALL(session_io_, IsConnectionAborted()).Times(AtLeast(1));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()))
+ .Times(AtLeast(1));
+
+ // The rest of these will have to happen in a fixed order.
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task));
+ EXPECT_CALL(*task, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task), SendDataFrame(task, "foobar", false),
+ SendDataFrame(task, "quux", true)));
+ ExpectSendSynReply(stream_id, false);
+ // Since the window size is just three bytes, we can only send three bytes at
+ // a time. The stream thread will then be blocked.
+ ExpectSendFrame(IsDataFrame(stream_id, false, "foo"));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, _))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::READ_CONNECTION_CLOSED));
+ // At this point, we're blocked on flow control, and the test will close the
+ // input side of the connection. Since the stream can never complete, the
+ // session should abort the stream and shut down, rather than staying blocked
+ // forever.
+ ExpectSendGoAway(stream_id, net::GOAWAY_OK);
+
+ session_->Run();
+}
+
+// Test that we send GOAWAY if the client tries to send
+// SETTINGS_INITIAL_WINDOW_SIZE with a value of 0.
+TEST_P(SpdySessionFlowControlTest, SendGoawayForTooSmallInitialWindowSize) {
+ ReceiveSettingsFrameFromClient(net::SETTINGS_INITIAL_WINDOW_SIZE, 0);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+
+ session_->Run();
+}
+
+// Test that we send GOAWAY if the client tries to send
+// SETTINGS_INITIAL_WINDOW_SIZE with a value of 0x80000000.
+TEST_P(SpdySessionFlowControlTest, SendGoawayForTooLargeInitialWindowSize) {
+ ReceiveSettingsFrameFromClient(net::SETTINGS_INITIAL_WINDOW_SIZE,
+ 0x80000000);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+
+ session_->Run();
+}
+
+TEST_P(SpdySessionFlowControlTest, SharedOutputFlowControlWindow) {
+ ReceiveWindowUpdateFrameFromClient(0, 10000);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ if (session_->spdy_version() >= mod_spdy::spdy::SPDY_VERSION_3_1) {
+ EXPECT_CALL(session_io_, IsConnectionAborted()).WillOnce(Return(true));
+ } else {
+ ExpectSendGoAway(0, net::GOAWAY_PROTOCOL_ERROR);
+ }
+
+ if (session_->spdy_version() >= mod_spdy::spdy::SPDY_VERSION_3_1) {
+ EXPECT_EQ(65536, session_->current_shared_output_window_size());
+ }
+ session_->Run();
+ if (session_->spdy_version() >= mod_spdy::spdy::SPDY_VERSION_3_1) {
+ EXPECT_EQ(75536, session_->current_shared_output_window_size());
+ }
+}
+
+TEST_P(SpdySessionFlowControlTest, SharedInputFlowControlWindow) {
+ MockStreamTask* task = new MockStreamTask;
+ const net::SpdyStreamId stream_id = 1;
+ const net::SpdyPriority priority = 2;
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_NONE);
+ const std::string data1(32000, 'x');
+ const std::string data2(2000, 'y');
+ ReceiveDataFromClient(stream_id, data1, net::DATA_FLAG_NONE);
+ ReceiveDataFromClient(stream_id, data1, net::DATA_FLAG_NONE);
+ ReceiveDataFromClient(stream_id, data2, net::DATA_FLAG_FIN);
+
+ EXPECT_CALL(session_io_, IsConnectionAborted()).Times(AtLeast(4));
+
+ // The rest of these will have to happen in a fixed order.
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ // Receive the SYN_STREAM from the client.
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task));
+ EXPECT_CALL(*task, Run()).WillOnce(ConsumeInputUntilAborted(task));
+ // Receive the first two blocks of data from the client with no problems.
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()));
+ // The third block of data is too much; it's a flow control error. For
+ // SPDY/3.1 and up it's a session flow control error; for SPDY/3 it's a
+ // stream flow control error.
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()));
+ if (session_->spdy_version() >= mod_spdy::spdy::SPDY_VERSION_3_1) {
+ ExpectSendGoAway(stream_id, net::GOAWAY_PROTOCOL_ERROR);
+ } else {
+ ExpectSendFrame(IsRstStream(1, net::RST_STREAM_FLOW_CONTROL_ERROR));
+ EXPECT_CALL(session_io_, IsConnectionAborted()).WillOnce(Return(true));
+ }
+
+ session_->Run();
+}
+
+// Only run flow control tests for SPDY v3 and up.
+INSTANTIATE_TEST_CASE_P(Spdy3, SpdySessionFlowControlTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_3, mod_spdy::spdy::SPDY_VERSION_3_1));
+
+// Create a type alias so that we can instantiate some of our
+// SpdySessionTest-based tests using a different set of parameters.
+typedef SpdySessionTest SpdySessionServerPushTest;
+
+TEST_P(SpdySessionServerPushTest, SimpleServerPush) {
+ MockStreamTask* task1 = new MockStreamTask;
+ MockStreamTask* task2 = new MockStreamTask;
+ executor_.set_run_on_add(true);
+ const net::SpdyStreamId stream_id = 3;
+ const net::SpdyPriority priority = 2;
+ const net::SpdyPriority push_priority = 3;
+ const std::string push_path = "/script.js";
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task1));
+ EXPECT_CALL(*task1, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task1),
+ StartServerPush(task1, push_priority, push_path,
+ mod_spdy::SpdyServerPushInterface::PUSH_STARTED),
+ SendDataFrame(task1, "foobar", false),
+ SendDataFrame(task1, "quux", true)));
+ // We should right away create the server push task, and get the SYN_STREAM
+ // before any other frames from the original stream.
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(2u)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id,
+ Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(push_priority)))))
+ .WillOnce(ReturnMockTask(task2));
+ EXPECT_CALL(*task2, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task2),
+ SendDataFrame(task2, "hello", false),
+ SendDataFrame(task2, "world", true)));
+ ExpectBeginServerPush(2u, stream_id, push_priority, push_path);
+ // The pushed stream has a low priority, so the rest of the first stream
+ // should get sent before the rest of the pushed stream.
+ ExpectSendSynReply(stream_id, false);
+ ExpectSendFrame(IsDataFrame(stream_id, false, "foobar"));
+ ExpectSendFrame(IsDataFrame(stream_id, true, "quux"));
+ // Now we should get the rest of the pushed stream.
+ ExpectSendHeaders(2u, false);
+ ExpectSendFrame(IsDataFrame(2u, false, "hello"));
+ ExpectSendFrame(IsDataFrame(2u, true, "world"));
+ // And, we're done.
+ EXPECT_CALL(session_io_, IsConnectionAborted());
+ EXPECT_CALL(session_io_, ProcessAvailableInput(Eq(true), NotNull()))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::READ_CONNECTION_CLOSED));
+ ExpectSendGoAway(stream_id, net::GOAWAY_OK);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+TEST_P(SpdySessionServerPushTest, TooManyConcurrentPushes) {
+ MockStreamTask* task1 = new MockStreamTask;
+ MockStreamTask* task2 = new MockStreamTask;
+ MockStreamTask* task3 = new MockStreamTask;
+ executor_.set_run_on_add(false);
+ const net::SpdyStreamId stream_id = 9;
+ const net::SpdyPriority priority = 0;
+ ReceiveSettingsFrameFromClient(net::SETTINGS_MAX_CONCURRENT_STREAMS, 2);
+ ReceiveSynStreamFromClient(stream_id, priority, net::CONTROL_FLAG_FIN);
+
+ EXPECT_CALL(session_io_, IsConnectionAborted()).Times(AtLeast(3));
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()))
+ .Times(AtLeast(3));
+
+ testing::InSequence seq;
+ ExpectSendFrame(IsSettings(net::SETTINGS_MAX_CONCURRENT_STREAMS, 100));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id, Eq(0u)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(priority)))))
+ .WillOnce(ReturnMockTask(task1));
+ EXPECT_CALL(session_io_, IsConnectionAborted())
+ .WillOnce(DoAll(InvokeWithoutArgs(&executor_, &InlineExecutor::RunOne),
+ Return(false)));
+ EXPECT_CALL(*task1, Run()).WillOnce(DoAll(
+ StartServerPush(task1, 3u, "/foo.css",
+ mod_spdy::SpdyServerPushInterface::PUSH_STARTED),
+ StartServerPush(task1, 2u, "/bar.css",
+ mod_spdy::SpdyServerPushInterface::PUSH_STARTED),
+ StartServerPush(task1, 1u, "/baz.css",
+ mod_spdy::SpdyServerPushInterface::TOO_MANY_CONCURRENT_PUSHES),
+ SendResponseHeaders(task1), SendDataFrame(task1, "html", true)));
+ // Start the first two pushes. The third push should fail due to too many
+ // concurrent pushes.
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(2u)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id,
+ Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(3u)))))
+ .WillOnce(ReturnMockTask(task2));
+ EXPECT_CALL(task_factory_, NewStreamTask(
+ AllOf(Property(&mod_spdy::SpdyStream::stream_id, Eq(4u)),
+ Property(&mod_spdy::SpdyStream::associated_stream_id,
+ Eq(stream_id)),
+ Property(&mod_spdy::SpdyStream::priority, Eq(2u)))))
+ .WillOnce(ReturnMockTask(task3));
+ // Now we get the SYN_STREAMs for the pushed streams before anything else.
+ ExpectBeginServerPush(2u, stream_id, 3u, "/foo.css");
+ ExpectBeginServerPush(4u, stream_id, 2u, "/bar.css");
+ // We now send the frames from the original stream.
+ ExpectSendSynReply(stream_id, false);
+ ExpectSendFrame(IsDataFrame(stream_id, true, "html"));
+ // At this point, the client will change MAX_CONCURRENT_STREAMS to zero. We
+ // shouldn't barf, even though we have more active push streams than the new
+ // maximum.
+ EXPECT_CALL(session_io_, IsConnectionAborted())
+ .WillOnce(DoAll(
+ SendBackSettings(this, net::SETTINGS_MAX_CONCURRENT_STREAMS, 0u),
+ Return(false)));
+ // Now let's run the rest of the tasks. One of them will try to start yet
+ // another server push, but that should fail because MAX_CONCURRENT_STREAMS
+ // is now zero.
+ EXPECT_CALL(session_io_, IsConnectionAborted())
+ .WillOnce(DoAll(InvokeWithoutArgs(&executor_, &InlineExecutor::RunAll),
+ Return(false)));
+ EXPECT_CALL(*task2, Run()).WillOnce(DoAll(
+ SendResponseHeaders(task2), SendDataFrame(task2, "foo", true)));
+ EXPECT_CALL(*task3, Run()).WillOnce(DoAll(
+ StartServerPush(task3, 3u, "/stuff.png",
+ mod_spdy::SpdyServerPushInterface::TOO_MANY_CONCURRENT_PUSHES),
+ SendResponseHeaders(task3), SendDataFrame(task3, "bar", true)));
+ // And now we get all those frames. The "bar" stream's frames should come
+ // first, because that's a higher-priority stream.
+ ExpectSendHeaders(4u, false);
+ ExpectSendFrame(IsDataFrame(4u, true, "bar"));
+ ExpectSendHeaders(2u, false);
+ ExpectSendFrame(IsDataFrame(2u, true, "foo"));
+ // And, we're done.
+ EXPECT_CALL(session_io_, ProcessAvailableInput(_, NotNull()))
+ .WillOnce(Return(mod_spdy::SpdySessionIO::READ_CONNECTION_CLOSED));
+ ExpectSendGoAway(stream_id, net::GOAWAY_OK);
+
+ session_.Run();
+ EXPECT_TRUE(executor_.stopped());
+}
+
+// Only run server push tests for SPDY v3 and up.
+INSTANTIATE_TEST_CASE_P(Spdy3, SpdySessionServerPushTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_3, mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
--- /dev/null
+// Copyright 2010 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.
+
+#include "mod_spdy/common/spdy_stream.h"
+
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/spdy_frame_queue.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// The smallest WINDOW_UPDATE delta we're willing to send. If the client sends
+// us less than this much data, we wait for more data before sending a
+// WINDOW_UPDATE frame (so that we don't end up sending lots of little ones).
+const size_t kMinWindowUpdateSize =
+ static_cast<size_t>(net::kSpdyStreamInitialWindowSize) / 8;
+
+class DataLengthVisitor : public net::SpdyFrameVisitor {
+ public:
+ DataLengthVisitor() : length_(0) {}
+ virtual ~DataLengthVisitor() {}
+
+ size_t length() const { return length_; }
+
+ virtual void VisitSynStream(const net::SpdySynStreamIR& frame) {}
+ virtual void VisitSynReply(const net::SpdySynReplyIR& frame) {}
+ virtual void VisitRstStream(const net::SpdyRstStreamIR& frame) {}
+ virtual void VisitSettings(const net::SpdySettingsIR& frame) {}
+ virtual void VisitPing(const net::SpdyPingIR& frame) {}
+ virtual void VisitGoAway(const net::SpdyGoAwayIR& frame) {}
+ virtual void VisitHeaders(const net::SpdyHeadersIR& frame) {}
+ virtual void VisitWindowUpdate(const net::SpdyWindowUpdateIR& frame) {}
+ virtual void VisitCredential(const net::SpdyCredentialIR& frame) {}
+ virtual void VisitBlocked(const net::SpdyBlockedIR& frame) {}
+ virtual void VisitPushPromise(const net::SpdyPushPromiseIR& frame) {}
+ virtual void VisitData(const net::SpdyDataIR& frame) {
+ length_ = frame.data().size();
+ }
+
+ private:
+ size_t length_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataLengthVisitor);
+};
+
+// For data frames, return the size of the data payload; for control frames,
+// return zero.
+size_t DataFrameLength(const net::SpdyFrameIR& frame) {
+ DataLengthVisitor visitor;
+ frame.Visit(&visitor);
+ return visitor.length();
+}
+
+} // namespace
+
+namespace mod_spdy {
+
+SpdyStream::SpdyStream(spdy::SpdyVersion spdy_version,
+ net::SpdyStreamId stream_id,
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ int32 initial_output_window_size,
+ SpdyFramePriorityQueue* output_queue,
+ SharedFlowControlWindow* shared_window,
+ SpdyServerPushInterface* pusher)
+ : spdy_version_(spdy_version),
+ stream_id_(stream_id),
+ associated_stream_id_(associated_stream_id),
+ server_push_depth_(server_push_depth),
+ priority_(priority),
+ output_queue_(output_queue),
+ shared_window_(shared_window),
+ pusher_(pusher),
+ condvar_(&lock_),
+ aborted_(false),
+ output_window_size_(initial_output_window_size),
+ // TODO(mdsteele): Make our initial input window size configurable (we
+ // would send the chosen value to the client with a SETTINGS frame).
+ input_window_size_(net::kSpdyStreamInitialWindowSize),
+ input_bytes_consumed_(0) {
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ DCHECK(output_queue_);
+ DCHECK(shared_window_ || spdy_version < spdy::SPDY_VERSION_3_1);
+ DCHECK(pusher_);
+ DCHECK_GT(output_window_size_, 0);
+ // In SPDY v2, priorities are in the range 0-3; in SPDY v3, they are 0-7.
+ DCHECK_GE(priority, 0u);
+ DCHECK_LE(priority, LowestSpdyPriorityForVersion(spdy_version));
+}
+
+SpdyStream::~SpdyStream() {}
+
+bool SpdyStream::is_server_push() const {
+ // By the SPDY spec, a stream has an even stream ID if and only if it was
+ // initiated by the server.
+ return stream_id_ % 2 == 0;
+}
+
+bool SpdyStream::is_aborted() const {
+ base::AutoLock autolock(lock_);
+ return aborted_;
+}
+
+void SpdyStream::AbortSilently() {
+ base::AutoLock autolock(lock_);
+ InternalAbortSilently();
+}
+
+void SpdyStream::AbortWithRstStream(net::SpdyRstStreamStatus status) {
+ base::AutoLock autolock(lock_);
+ InternalAbortWithRstStream(status);
+}
+
+int32 SpdyStream::current_input_window_size() const {
+ base::AutoLock autolock(lock_);
+ DCHECK_GE(spdy_version(), spdy::SPDY_VERSION_3);
+ return input_window_size_;
+}
+
+int32 SpdyStream::current_output_window_size() const {
+ base::AutoLock autolock(lock_);
+ DCHECK_GE(spdy_version(), spdy::SPDY_VERSION_3);
+ return output_window_size_;
+}
+
+void SpdyStream::OnInputDataConsumed(size_t size) {
+ // Sanity check: there is no input data to absorb for a server push stream,
+ // so we should only be getting called for client-initiated streams.
+ DCHECK(!is_server_push());
+
+ // Flow control only exists for SPDY v3 and up, so for SPDY v2 we don't need
+ // to bother tracking this.
+ if (spdy_version() < spdy::SPDY_VERSION_3) {
+ return;
+ }
+
+ // If the size arg is zero, this method should be a no-op, so just quit now.
+ if (size == 0) {
+ return;
+ }
+
+ base::AutoLock autolock(lock_);
+
+ // Don't bother with any of this if the stream has been aborted.
+ if (aborted_) {
+ return;
+ }
+
+ // First, if we're using SPDY/3.1 or later, we need to deal with the shared
+ // session window. If after consuming this input data the shared window
+ // thinks it's time to send a WINDOW_UPDATE for the session input window
+ // (stream 0), send one, at top priority.
+ if (spdy_version_ >= spdy::SPDY_VERSION_3_1) {
+ const int32 shared_window_update =
+ shared_window_->OnInputDataConsumed(size);
+ if (shared_window_update > 0) {
+ output_queue_->Insert(
+ SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyWindowUpdateIR(0, shared_window_update));
+ }
+ }
+
+ // Make sure the current input window size is sane. Although there are
+ // provisions in the SPDY spec that allow the window size to be temporarily
+ // negative, or to go above its default initial size, with our current
+ // implementation that should never happen. Once we make the initial input
+ // window size configurable, we may need to adjust or remove these checks.
+ DCHECK_GE(input_window_size_, 0);
+ DCHECK_LE(input_window_size_, net::kSpdyStreamInitialWindowSize);
+
+ // Add the newly consumed data to the total. Assuming our caller is behaving
+ // well (even if the client isn't) -- that is, they are only consuming as
+ // much data as we have put into the input queue -- there should be no
+ // overflow here, and the new value should be at most the amount of
+ // un-WINDOW_UPDATE-ed data we've received. The reason we can be sure of
+ // this is that PostInputFrame() refuses to put more data into the queue than
+ // the window size allows, and aborts the stream if the client tries.
+ input_bytes_consumed_ += size;
+ DCHECK_GE(input_bytes_consumed_, size);
+ DCHECK_LE(input_bytes_consumed_,
+ static_cast<size_t>(net::kSpdyStreamInitialWindowSize -
+ input_window_size_));
+
+ // We don't want to send lots of little WINDOW_UPDATE frames (as that would
+ // waste bandwidth), so only bother sending one once it would have a
+ // reasonably large value.
+ // TODO(mdsteele): Consider also tracking whether we have received a FLAG_FIN
+ // on this stream; once we've gotten FLAG_FIN, there will be no more data,
+ // so we don't need to send any more WINDOW_UPDATE frames.
+ if (input_bytes_consumed_ < kMinWindowUpdateSize) {
+ return;
+ }
+
+ // The SPDY spec forbids sending WINDOW_UPDATE frames with a non-positive
+ // delta-window-size (SPDY draft 3 section 2.6.8). But since we already
+ // checked above that size was positive, input_bytes_consumed_ should now be
+ // positive as well.
+ DCHECK_GT(input_bytes_consumed_, 0u);
+ // Make sure there won't be any overflow shenanigans.
+ COMPILE_ASSERT(sizeof(size_t) >= sizeof(net::kSpdyMaximumWindowSize),
+ size_t_is_at_least_32_bits);
+ DCHECK_LE(input_bytes_consumed_,
+ static_cast<size_t>(net::kSpdyMaximumWindowSize));
+
+ // Send a WINDOW_UPDATE frame to the client and update our window size.
+ SendOutputFrame(new net::SpdyWindowUpdateIR(
+ stream_id_, input_bytes_consumed_));
+ input_window_size_ += input_bytes_consumed_;
+ DCHECK_LE(input_window_size_, net::kSpdyStreamInitialWindowSize);
+ input_bytes_consumed_ = 0;
+}
+
+void SpdyStream::AdjustOutputWindowSize(int32 delta) {
+ base::AutoLock autolock(lock_);
+
+ // Flow control only exists for SPDY v3 and up.
+ DCHECK_GE(spdy_version(), spdy::SPDY_VERSION_3);
+
+ if (aborted_) {
+ return;
+ }
+
+ // Check for overflow; if it happens, abort the stream (which will wake up
+ // any blocked threads). Note that although delta is usually positive, it
+ // can also be negative, so we check for both overflow and underflow.
+ const int64 new_size =
+ static_cast<int64>(output_window_size_) + static_cast<int64>(delta);
+ if (new_size > static_cast<int64>(net::kSpdyMaximumWindowSize) ||
+ new_size < -static_cast<int64>(net::kSpdyMaximumWindowSize)) {
+ LOG(WARNING) << "Flow control overflow/underflow on stream "
+ << stream_id_ << ". Aborting stream.";
+ InternalAbortWithRstStream(net::RST_STREAM_FLOW_CONTROL_ERROR);
+ return;
+ }
+
+ // Update the window size.
+ const int32 old_size = output_window_size_;
+ output_window_size_ = static_cast<int32>(new_size);
+
+ // If the window size is newly positive, wake up any blocked threads.
+ if (old_size <= 0 && output_window_size_ > 0) {
+ condvar_.Broadcast();
+ }
+}
+
+void SpdyStream::PostInputFrame(net::SpdyFrameIR* frame_ptr) {
+ base::AutoLock autolock(lock_);
+
+ // Take ownership of the frame, so it will get deleted if we return early.
+ scoped_ptr<net::SpdyFrameIR> frame(frame_ptr);
+
+ // Once a stream has been aborted, nothing more goes into the queue.
+ if (aborted_) {
+ return;
+ }
+
+ // If this is a nonempty data frame (and we're using SPDY v3 or above) we
+ // need to track flow control.
+ if (spdy_version() >= spdy::SPDY_VERSION_3) {
+ DCHECK_GE(input_window_size_, 0);
+ const int size = DataFrameLength(*frame); // returns zero for ctrl frames
+ if (size > 0) {
+ // If receiving this much data would overflow the window size, then abort
+ // the stream with a flow control error.
+ if (size > input_window_size_) {
+ LOG(WARNING) << "Client violated flow control by sending too much data "
+ << "to stream " << stream_id_ << ". Aborting stream.";
+ InternalAbortWithRstStream(net::RST_STREAM_FLOW_CONTROL_ERROR);
+ return; // Quit without posting the frame to the queue.
+ }
+ // Otherwise, decrease the window size. It will be increased again once
+ // the data has been comsumed (by OnInputDataConsumed()).
+ else {
+ input_window_size_ -= size;
+ }
+ }
+ }
+
+ // Now that we've decreased the window size as necessary, we can make the
+ // frame available for consumption by the stream thread.
+ input_queue_.Insert(frame.release());
+}
+
+bool SpdyStream::GetInputFrame(bool block, net::SpdyFrameIR** frame) {
+ return input_queue_.Pop(block, frame);
+}
+
+void SpdyStream::SendOutputSynStream(const net::SpdyHeaderBlock& headers,
+ bool flag_fin) {
+ DCHECK(is_server_push());
+ base::AutoLock autolock(lock_);
+ if (aborted_) {
+ return;
+ }
+
+ scoped_ptr<net::SpdySynStreamIR> frame(new net::SpdySynStreamIR(stream_id_));
+ frame->set_associated_to_stream_id(associated_stream_id_);
+ frame->set_priority(priority_);
+ frame->set_fin(flag_fin);
+ frame->set_unidirectional(true);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ output_queue_->Insert(SpdyFramePriorityQueue::kTopPriority, frame.release());
+}
+
+void SpdyStream::SendOutputSynReply(const net::SpdyHeaderBlock& headers,
+ bool flag_fin) {
+ DCHECK(!is_server_push());
+ base::AutoLock autolock(lock_);
+ if (aborted_) {
+ return;
+ }
+
+ scoped_ptr<net::SpdySynReplyIR> frame(new net::SpdySynReplyIR(stream_id_));
+ frame->set_fin(flag_fin);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ SendOutputFrame(frame.release());
+}
+
+void SpdyStream::SendOutputHeaders(const net::SpdyHeaderBlock& headers,
+ bool flag_fin) {
+ base::AutoLock autolock(lock_);
+ if (aborted_) {
+ return;
+ }
+
+ scoped_ptr<net::SpdyHeadersIR> frame(new net::SpdyHeadersIR(stream_id_));
+ frame->set_fin(flag_fin);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ SendOutputFrame(frame.release());
+}
+
+void SpdyStream::SendOutputDataFrame(base::StringPiece data, bool flag_fin) {
+ base::AutoLock autolock(lock_);
+ if (aborted_) {
+ return;
+ }
+
+ // Flow control only exists for SPDY v3 and up; for SPDY v2, we can just send
+ // the data without regard to the window size. Even with flow control, we
+ // can of course send empty DATA frames at will.
+ if (spdy_version() < spdy::SPDY_VERSION_3 || data.empty()) {
+ // Suppress empty DATA frames (unless we're setting FLAG_FIN).
+ if (!data.empty() || flag_fin) {
+ scoped_ptr<net::SpdyDataIR> frame(new net::SpdyDataIR(stream_id_, data));
+ frame->set_fin(flag_fin);
+ SendOutputFrame(frame.release());
+ }
+ return;
+ }
+
+ while (!data.empty()) {
+ // If the current window size is non-positive, we must wait to send data
+ // until the client increases it (or we abort). Note that the window size
+ // can be negative if the client decreased the maximum window size (with a
+ // SETTINGS frame) after we already sent data (SPDY draft 3 section 2.6.8).
+ while (!aborted_ && output_window_size_ <= 0) {
+ condvar_.Wait();
+ }
+ if (aborted_) {
+ return;
+ }
+ // If the current window size is less than the amount of data we'd like to
+ // send, send a smaller data frame with the first part of the data, and
+ // then we'll sleep until the window size is increased before sending the
+ // rest.
+ DCHECK_LE(data.size(), static_cast<size_t>(kint32max));
+ const int32 full_length = data.size();
+ DCHECK_GT(output_window_size_, 0);
+ const int32 length_desired = std::min(full_length, output_window_size_);
+ output_window_size_ -= length_desired;
+ DCHECK_GE(output_window_size_, 0);
+ // Now we need to request quota from the session-shared flow control
+ // window. Since the call to RequestQuota may block, we need to unlock
+ // first.
+ int32 length_acquired;
+ if (spdy_version() >= spdy::SPDY_VERSION_3_1) {
+ base::AutoUnlock autounlock(lock_);
+ DCHECK(shared_window_);
+ length_acquired = shared_window_->RequestOutputQuota(length_desired);
+ } else {
+ // For SPDY versions that don't have a session window, just act like we
+ // got the quota we wanted.
+ length_acquired = length_desired;
+ }
+ // RequestQuota will return zero if the shared window has been aborted
+ // (i.e. if the session has been aborted). So in that case let's just
+ // abort too.
+ if (length_acquired <= 0) {
+ InternalAbortSilently();
+ return;
+ }
+ // If we didn't acquire as much as we wanted from the shared window, put
+ // the amount we're not actually using back into output_window_size_.
+ else if (length_acquired < length_desired) {
+ output_window_size_ += length_desired - length_acquired;
+ }
+ // Actually send the frame.
+ scoped_ptr<net::SpdyDataIR> frame(
+ new net::SpdyDataIR(stream_id_, data.substr(0, length_acquired)));
+ frame->set_fin(flag_fin && length_acquired == full_length);
+ SendOutputFrame(frame.release());
+ data = data.substr(length_acquired);
+ }
+}
+
+SpdyServerPushInterface::PushStatus SpdyStream::StartServerPush(
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers) {
+ DCHECK_GE(spdy_version(), spdy::SPDY_VERSION_3);
+ return pusher_->StartServerPush(stream_id_, server_push_depth_ + 1, priority,
+ request_headers);
+}
+
+void SpdyStream::SendOutputFrame(net::SpdyFrameIR* frame) {
+ lock_.AssertAcquired();
+ DCHECK(!aborted_);
+ output_queue_->Insert(static_cast<int>(priority_), frame);
+}
+
+void SpdyStream::InternalAbortSilently() {
+ lock_.AssertAcquired();
+ input_queue_.Abort();
+ aborted_ = true;
+ condvar_.Broadcast();
+}
+
+void SpdyStream::InternalAbortWithRstStream(net::SpdyRstStreamStatus status) {
+ lock_.AssertAcquired();
+ output_queue_->Insert(SpdyFramePriorityQueue::kTopPriority,
+ new net::SpdyRstStreamIR(stream_id_, status));
+ // InternalAbortSilently will set aborted_ to true, which will prevent the
+ // stream thread from sending any more frames on this stream after the
+ // RST_STREAM.
+ InternalAbortSilently();
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_STREAM_H_
+#define MOD_SPDY_COMMON_SPDY_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_frame_queue.h"
+#include "mod_spdy/common/spdy_server_push_interface.h"
+
+namespace mod_spdy {
+
+class SharedFlowControlWindow;
+class SpdyFramePriorityQueue;
+
+// Represents one stream of a SPDY connection. This class is used to
+// coordinate and pass SPDY frames between the SPDY-to-HTTP filter, the
+// HTTP-to-SPDY filter, and the master SPDY connection thread. This class is
+// thread-safe, and in particular can be used concurrently by the stream thread
+// and the connection thread (although certain methods are meant to only ever
+// be called by one thread or the other; see the doc comments).
+class SpdyStream {
+ public:
+ // The SpdyStream object does *not* take ownership of any of these arguments.
+ SpdyStream(spdy::SpdyVersion spdy_version,
+ net::SpdyStreamId stream_id,
+ net::SpdyStreamId associated_stream_id_,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ int32 initial_output_window_size,
+ SpdyFramePriorityQueue* output_queue,
+ SharedFlowControlWindow* shared_window,
+ SpdyServerPushInterface* pusher);
+ ~SpdyStream();
+
+ // What version of SPDY is being used for this connection?
+ spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+
+ // Return true if this stream was initiated by the server, false if it was
+ // initiated by the client.
+ bool is_server_push() const;
+
+ // Get the ID for this SPDY stream.
+ net::SpdyStreamId stream_id() const { return stream_id_; }
+
+ // Get the ID for the SPDY stream with which this one is associated. By the
+ // SPDY spec, if there is no associated stream, this will be zero.
+ net::SpdyStreamId associated_stream_id() const {
+ return associated_stream_id_;
+ }
+
+ // Get the current depth of the stream. 0 if this is a client initiated
+ // stream or associated_stream.depth+1 if this stream was created as
+ // a result of another stream.
+ int32 server_push_depth() const { return server_push_depth_; }
+
+ // Get the priority of this stream.
+ net::SpdyPriority priority() const { return priority_; }
+
+ // Return true if this stream has been aborted and should shut down.
+ bool is_aborted() const;
+
+ // Abort this stream. This method returns immediately, and the thread
+ // running the stream will stop as soon as possible (if it is currently
+ // blocked on the window size, it will be woken up).
+ void AbortSilently();
+
+ // Same as AbortSilently, but also sends a RST_STREAM frame for this stream.
+ void AbortWithRstStream(net::SpdyRstStreamStatus status);
+
+ // What are the current window sizes for this stream? These are mostly
+ // useful for debugging. Requires that spdy_version() >= SPDY_VERSION_3.
+ int32 current_input_window_size() const;
+ int32 current_output_window_size() const;
+
+ // This should be called by the stream thread for each chunk of input data
+ // that it consumes. The SpdyStream object will take care of sending
+ // WINDOW_UPDATE frames as appropriate (automatically bunching up smaller,
+ // chunks to avoid sending too many frames, and of course not sending
+ // WINDOW_UPDATE frames for SPDY/2 connections). The connection thread must
+ // not call this method.
+ void OnInputDataConsumed(size_t size);
+
+ // This should be called by the connection thread to adjust the window size,
+ // either due to receiving a WINDOW_UPDATE frame from the client, or from the
+ // client changing the initial window size with a SETTINGS frame. The delta
+ // argument will usually be positive (WINDOW_UPDATE is always positive), but
+ // *can* be negative (if the client reduces the window size with SETTINGS).
+ //
+ // This method should *not* be called by the stream thread; the SpdyStream
+ // object will automatically take care of decreasing the window size for sent
+ // data.
+ void AdjustOutputWindowSize(int32 delta);
+
+ // Provide a SPDY frame sent from the client. This is to be called from the
+ // master connection thread. This method takes ownership of the frame
+ // object.
+ void PostInputFrame(net::SpdyFrameIR* frame);
+
+ // Get a SPDY frame from the client and return true, or return false if no
+ // frame is available. If the block argument is true and no frame is
+ // currently available, block until a frame becomes available or the stream
+ // is aborted. This is to be called from the stream thread. The caller
+ // gains ownership of the provided frame.
+ bool GetInputFrame(bool block, net::SpdyFrameIR** frame);
+
+ // Send a SYN_STREAM frame to the client for this stream. This may only be
+ // called if is_server_push() is true.
+ void SendOutputSynStream(const net::SpdyHeaderBlock& headers, bool flag_fin);
+
+ // Send a SYN_REPLY frame to the client for this stream. This may only be
+ // called if is_server_push() is false.
+ void SendOutputSynReply(const net::SpdyHeaderBlock& headers, bool flag_fin);
+
+ // Send a HEADERS frame to the client for this stream.
+ void SendOutputHeaders(const net::SpdyHeaderBlock& headers, bool flag_fin);
+
+ // Send a SPDY data frame to the client on this stream.
+ void SendOutputDataFrame(base::StringPiece data, bool flag_fin);
+
+ // Initiate a SPDY server push associated with this stream, roughly by
+ // pretending that the client sent a SYN_STREAM with the given headers. To
+ // repeat: the headers argument is _not_ the headers that the server will
+ // send to the client, but rather the headers to _pretend_ that the client
+ // sent to the server. Requires that spdy_version() >= 3.
+ SpdyServerPushInterface::PushStatus StartServerPush(
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers);
+
+ private:
+ // Send a SPDY frame to the client. This is to be called from the stream
+ // thread. This method takes ownership of the frame object. Must be holding
+ // lock_ to call this method.
+ void SendOutputFrame(net::SpdyFrameIR* frame);
+
+ // Aborts the input queue, sets aborted_, and wakes up threads waiting on
+ // condvar_. Must be holding lock_ to call this method.
+ void InternalAbortSilently();
+
+ // Like InternalAbortSilently, but also sends a RST_STREAM frame for this
+ // stream. Must be holding lock_ to call this method.
+ void InternalAbortWithRstStream(net::SpdyRstStreamStatus status);
+
+ // These fields are all either constant or thread-safe, and do not require
+ // additional synchronization.
+ const spdy::SpdyVersion spdy_version_;
+ const net::SpdyStreamId stream_id_;
+ const net::SpdyStreamId associated_stream_id_;
+ const int32 server_push_depth_;
+ const net::SpdyPriority priority_;
+ SpdyFrameQueue input_queue_;
+ SpdyFramePriorityQueue* const output_queue_;
+ SharedFlowControlWindow* const shared_window_;
+ SpdyServerPushInterface* const pusher_;
+
+ // The lock protects the fields below. The above fields do not require
+ // additional synchronization.
+ mutable base::Lock lock_;
+ base::ConditionVariable condvar_;
+ bool aborted_;
+ int32 output_window_size_;
+ int32 input_window_size_;
+ size_t input_bytes_consumed_; // consumed since we last sent a WINDOW_UPDATE
+ size_t input_bytes_unconsumed_; // received but not yet consumed
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyStream);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_STREAM_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_stream_task_factory.h"
+
+namespace mod_spdy {
+
+SpdyStreamTaskFactory::SpdyStreamTaskFactory() {}
+
+SpdyStreamTaskFactory::~SpdyStreamTaskFactory() {}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2011 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.
+
+#ifndef MOD_SPDY_COMMON_SPDY_STREAM_TASK_FACTORY_H_
+#define MOD_SPDY_COMMON_SPDY_STREAM_TASK_FACTORY_H_
+
+#include "base/basictypes.h"
+
+namespace net_instaweb { class Function; }
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// SpdyStreamTaskFactory is a helper interface for the SpdySession class.
+// The task factory generates tasks that take care of processing SPDY streams.
+// The task factory must not be deleted before all such tasks have been
+// disposed of (run or cancelled).
+class SpdyStreamTaskFactory {
+ public:
+ SpdyStreamTaskFactory();
+ virtual ~SpdyStreamTaskFactory();
+
+ // Create a new task to process the given stream. Running the task should
+ // process the stream -- that is, pull frames off the stream's input queue
+ // and post frames to the stream's output queue -- and the task should not
+ // complete until the stream is completely finished.
+ //
+ // The implementation may assume that the factory will outlive the task.
+ virtual net_instaweb::Function* NewStreamTask(SpdyStream* stream) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyStreamTaskFactory);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_STREAM_TASK_FACTORY_H_
--- /dev/null
+// Copyright 2011 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.
+
+#include "mod_spdy/common/spdy_stream.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using mod_spdy::testing::IsDataFrame;
+using mod_spdy::testing::IsRstStream;
+using mod_spdy::testing::IsWindowUpdate;
+
+namespace {
+
+const net::SpdyStreamId kStreamId = 1;
+const net::SpdyStreamId kAssocStreamId = 0;
+const int32 kInitServerPushDepth = 0;
+const net::SpdyPriority kPriority = 2;
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+ MOCK_METHOD4(StartServerPush,
+ mod_spdy::SpdyServerPushInterface::PushStatus(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyNameValueBlock& request_headers));
+};
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// data frame with the given payload and FLAG_FIN setting.
+void ExpectDataFrame(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ base::StringPiece data, bool flag_fin) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsDataFrame(kStreamId, flag_fin, data));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// RST_STREAM frame with the given status code.
+void ExpectRstStream(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ net::SpdyRstStreamStatus status) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsRstStream(kStreamId, status));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// WINDOW_UPDATE frame with the given delta.
+void ExpectWindowUpdate(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ uint32 delta) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsWindowUpdate(kStreamId, delta));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// WINDOW_UPDATE frame, for stream zero, with the given delta.
+void ExpectSessionWindowUpdate(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ uint32 delta) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsWindowUpdate(0, delta));
+}
+
+// When run, a SendDataTask sends the given data to the given stream.
+class SendDataTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+ SendDataTask(mod_spdy::SpdyStream* stream, base::StringPiece data,
+ bool flag_fin)
+ : stream_(stream), data_(data), flag_fin_(flag_fin) {}
+ virtual void Run() {
+ stream_->SendOutputDataFrame(data_, flag_fin_);
+ }
+ private:
+ mod_spdy::SpdyStream* const stream_;
+ const base::StringPiece data_;
+ const bool flag_fin_;
+ DISALLOW_COPY_AND_ASSIGN(SendDataTask);
+};
+
+// Test that the flow control features are disabled for SPDY v2.
+TEST(SpdyStreamTest, NoFlowControlInSpdy2) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ stream.SendOutputDataFrame(data, true);
+
+ // We should get all the data out in one frame anyway, because we're using
+ // SPDY v2 and the stream shouldn't be using flow control.
+ ExpectDataFrame(&output_queue, data, true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that flow control works correctly for SPDY/3.
+TEST(SpdyStreamTest, HasFlowControlInSpdy3) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 7);
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ &shared_window, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // We should get a single frame out with the first initial_window_size=10
+ // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+ ExpectDataFrame(&output_queue, "abcdefghij", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+
+ // After increasing the window size by eight, we should get eight more bytes,
+ // and then we should still be blocked.
+ stream.AdjustOutputWindowSize(8);
+ ExpectDataFrame(&output_queue, "klmnopqr", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+
+ // Finally, we increase the window size by fifteen. We should get the last
+ // eight bytes of data out (with FLAG_FIN now set), the task should be
+ // completed, and the remaining window size should be seven.
+ stream.AdjustOutputWindowSize(15);
+ ExpectDataFrame(&output_queue, "stuvwxyz", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(7, stream.current_output_window_size());
+}
+
+// Test that the session flow control window works correctly for SPDY/3.1.
+TEST(SpdyStreamTest, SessionWindowInSpdy31) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 7);
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3_1, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size,
+ &output_queue, &shared_window, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // The stream window size is 10, but the session window size is only 7. So
+ // we should only get 7 bytes at first.
+ ExpectDataFrame(&output_queue, "abcdefg", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ // Now we increase the shared window size to 8. The stream window size is
+ // only 3, so we should get just 3 more bytes.
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(8));
+ ExpectDataFrame(&output_queue, "hij", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(5, shared_window.current_output_window_size());
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Next, increase the stream window by 20 bytes. The shared window is only
+ // 5, so we get 5 bytes.
+ stream.AdjustOutputWindowSize(20);
+ ExpectDataFrame(&output_queue, "klmno", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ // Finally, we increase the shared window size by 20. We should get the last
+ // 11 bytes of data out (with FLAG_FIN now set), and the task should be
+ // completed.
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(20));
+ ExpectDataFrame(&output_queue, "pqrstuvwxyz", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(9, shared_window.current_output_window_size());
+ EXPECT_EQ(4, stream.current_output_window_size());
+}
+
+// Test that flow control is well-behaved when the stream is aborted.
+TEST(SpdyStreamTest, FlowControlAbort) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 7;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // We should get a single frame out with the first initial_window_size=7
+ // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+ ExpectDataFrame(&output_queue, "abcdefg", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_FALSE(stream.is_aborted());
+
+ // We now abort with a RST_STREAM frame. We should get the RST_STREAM frame
+ // out, but no more data, and the call to SendOutputDataFrame should return
+ // even though the rest of the data was never sent.
+ stream.AbortWithRstStream(net::RST_STREAM_PROTOCOL_ERROR);
+ EXPECT_TRUE(stream.is_aborted());
+ ExpectRstStream(&output_queue, net::RST_STREAM_PROTOCOL_ERROR);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+
+ // Now that we're aborted, any attempt to send more frames should be ignored.
+ stream.SendOutputDataFrame("foobar", false);
+ net::SpdyNameValueBlock headers;
+ headers["x-foo"] = "bar";
+ stream.SendOutputHeaders(headers, true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that we abort the stream with FLOW_CONTROL_ERROR if the client
+// incorrectly overflows the 31-bit window size value.
+TEST(SpdyStreamTest, FlowControlOverflow) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, 0x60000000, &output_queue, NULL,
+ &pusher);
+
+ // Increase the window size so large that it overflows. We should get a
+ // RST_STREAM frame and the stream should be aborted.
+ EXPECT_FALSE(stream.is_aborted());
+ stream.AdjustOutputWindowSize(0x20000000);
+ EXPECT_TRUE(stream.is_aborted());
+ ExpectRstStream(&output_queue, net::RST_STREAM_FLOW_CONTROL_ERROR);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that flow control works correctly even if the window size is
+// temporarily negative.
+TEST(SpdyStreamTest, NegativeWindowSize) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // We should get a single frame out with the first initial_window_size=10
+ // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+ ExpectDataFrame(&output_queue, "abcdefghij", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Adjust the window size down (as if due to a SETTINGS frame reducing the
+ // initial window size). Our current window size should now be negative, and
+ // we should still be blocked.
+ stream.AdjustOutputWindowSize(-5);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(-5, stream.current_output_window_size());
+
+ // Adjust the initial window size up, but not enough to be positive. We
+ // should still be blocked.
+ stream.AdjustOutputWindowSize(4);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(-1, stream.current_output_window_size());
+
+ // Adjust the initial window size up again. Now we should get a few more
+ // bytes out.
+ stream.AdjustOutputWindowSize(4);
+ ExpectDataFrame(&output_queue, "klm", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Finally, open the floodgates; we should get the rest of the data.
+ stream.AdjustOutputWindowSize(800);
+ ExpectDataFrame(&output_queue, "nopqrstuvwxyz", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(787, stream.current_output_window_size());
+}
+
+// Test that we handle sending empty DATA frames correctly in SPDY v2.
+TEST(SpdyStreamTest, SendEmptyDataFrameInSpdy2) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // Try to send an empty data frame without FLAG_FIN. It should be
+ // suppressed.
+ stream.SendOutputDataFrame("", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+
+ // Now send an empty data frame _with_ FLAG_FIN. It should _not_ be
+ // suppressed.
+ stream.SendOutputDataFrame("", true);
+ ExpectDataFrame(&output_queue, "", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that we handle sending empty DATA frames correctly in SPDY v3.
+TEST(SpdyStreamTest, SendEmptyDataFrameInSpdy3) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Try to send an empty data frame without FLAG_FIN. It should be
+ // suppressed.
+ stream.SendOutputDataFrame("", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(initial_window_size, stream.current_output_window_size());
+
+ // Send one window's worth of data. It should get sent successfully.
+ const std::string data(initial_window_size, 'x');
+ stream.SendOutputDataFrame(data, false);
+ ExpectDataFrame(&output_queue, data, false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Try to send another empty data frame without FLAG_FIN. It should be
+ // suppressed, and we shouldn't block, even though the window size is zero.
+ stream.SendOutputDataFrame("", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Now send an empty data frame _with_ FLAG_FIN. It should _not_ be
+ // suppressed, and we still shouldn't block.
+ stream.SendOutputDataFrame("", true);
+ ExpectDataFrame(&output_queue, "", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(0, stream.current_output_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlInSpdy3) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // The initial window size is 64K.
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Post a SYN_STREAM frame to the input. This should not affect the input
+ // window size.
+ net::SpdyNameValueBlock request_headers;
+ request_headers[mod_spdy::http::kContentLength] = "4000";
+ request_headers[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ request_headers[mod_spdy::spdy::kSpdy3Method] = "GET";
+ request_headers[mod_spdy::spdy::kSpdy3Path] = "/index.html";
+ request_headers[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ scoped_ptr<net::SpdySynStreamIR> syn_stream(
+ new net::SpdySynStreamIR(kStreamId));
+ syn_stream->set_associated_to_stream_id(kAssocStreamId);
+ syn_stream->set_priority(kPriority);
+ syn_stream->GetMutableNameValueBlock()->insert(
+ request_headers.begin(), request_headers.end());
+ stream.PostInputFrame(syn_stream.release());
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Send a little bit of data. This should reduce the input window size.
+ const std::string data1("abcdefghij");
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed this data. However, we shouldn't
+ // yet send a WINDOW_UPDATE frame for so small an amount, so the window size
+ // should stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Send the rest of the data. This should further reduce the input window
+ // size.
+ const std::string data2(9000, 'x');
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(kStreamId, data2));
+ data_frame->set_fin(true);
+ stream.PostInputFrame(data_frame.release());
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed a bit more of the data. However,
+ // we still shouldn't yet send a WINDOW_UPDATE frame, and the window size
+ // should still stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Now say that we've consumed a whole bunch of data. At this point, we
+ // should get a WINDOW_UPDATE frame for everything consumed so far, and the
+ // window size should increase accordingly.
+ stream.OnInputDataConsumed(8900);
+ ExpectWindowUpdate(&output_queue, 8920);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+
+ // Consume the last of the data. This is now just a little bit, so no need
+ // for a WINDOW_UPDATE here.
+ stream.OnInputDataConsumed(90);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlInSpdy31) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ mod_spdy::SharedFlowControlWindow shared_window(
+ net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize);
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3_1, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, &shared_window, &pusher);
+
+ // The initial window size is 64K.
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Post a SYN_STREAM frame to the input. This should not affect the input
+ // window size.
+ net::SpdyHeaderBlock request_headers;
+ request_headers[mod_spdy::http::kContentLength] = "4000";
+ request_headers[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ request_headers[mod_spdy::spdy::kSpdy3Method] = "GET";
+ request_headers[mod_spdy::spdy::kSpdy3Path] = "/index.html";
+ request_headers[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+
+ scoped_ptr<net::SpdySynStreamIR> syn_stream(
+ new net::SpdySynStreamIR(kStreamId));
+ syn_stream->set_associated_to_stream_id(kAssocStreamId);
+ syn_stream->set_priority(kPriority);
+ syn_stream->GetMutableNameValueBlock()->insert(
+ request_headers.begin(), request_headers.end());
+ stream.PostInputFrame(syn_stream.release());
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Send a little bit of data. This should reduce the input window size.
+ const std::string data1("abcdefghij");
+ EXPECT_TRUE(shared_window.OnReceiveInputData(data1.size()));
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed this data. However, we shouldn't
+ // yet send a WINDOW_UPDATE frame for so small an amount, so the window size
+ // should stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Send the rest of the data. This should further reduce the input window
+ // size.
+ const std::string data2(9000, 'x');
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(kStreamId, data2));
+ data_frame->set_fin(true);
+ EXPECT_TRUE(shared_window.OnReceiveInputData(data2.size()));
+ stream.PostInputFrame(data_frame.release());
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed a bit more of the data. However,
+ // we still shouldn't yet send a WINDOW_UPDATE frame, and the window size
+ // should still stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Now say that we've consumed a whole bunch of data. At this point, we
+ // should get a WINDOW_UPDATE frame for everything consumed so far, and the
+ // window size should increase accordingly.
+ stream.OnInputDataConsumed(8900);
+ ExpectSessionWindowUpdate(&output_queue, 8920);
+ ExpectWindowUpdate(&output_queue, 8920);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+
+ // Consume the last of the data. This is now just a little bit, so no need
+ // for a WINDOW_UPDATE here.
+ stream.OnInputDataConsumed(90);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlError) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // Send a bunch of data. This should reduce the input window size.
+ const std::string data1(1000, 'x');
+ for (int i = 0; i < 65; ++i) {
+ EXPECT_EQ(65536 - i * 1000, stream.current_input_window_size());
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ }
+ EXPECT_EQ(536, stream.current_input_window_size());
+ EXPECT_FALSE(stream.is_aborted());
+
+ // Send a bit more data than there is room in the window size. This should
+ // trigger a RST_STREAM.
+ const std::string data2(537, 'y');
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data2));
+ ExpectRstStream(&output_queue, net::RST_STREAM_FLOW_CONTROL_ERROR);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_TRUE(stream.is_aborted());
+}
+
+TEST(SpdyStreamTest, NoInputFlowControlInSpdy2) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // Send more data than will fit in the window size. However, we shouldn't
+ // get an error, because this is SPDY/2 and there is no flow control.
+ const std::string data1(1000, 'x');
+ for (int i = 0; i < 70; ++i) {
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_FALSE(stream.is_aborted());
+ }
+}
+
+} // namespace
--- /dev/null
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/spdy_to_http_converter.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h" // for Int64ToString
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace {
+
+// Generate an HTTP request line from the given SPDY header block by calling
+// the OnStatusLine() method of the given visitor, and return true. If there's
+// an error, this will return false without calling any methods on the visitor.
+bool GenerateRequestLine(spdy::SpdyVersion spdy_version,
+ const net::SpdyHeaderBlock& block,
+ HttpRequestVisitorInterface* visitor) {
+ const bool spdy2 = spdy_version < spdy::SPDY_VERSION_3;
+ net::SpdyHeaderBlock::const_iterator method = block.find(
+ spdy2 ? spdy::kSpdy2Method : spdy::kSpdy3Method);
+ net::SpdyHeaderBlock::const_iterator scheme = block.find(
+ spdy2 ? spdy::kSpdy2Scheme : spdy::kSpdy3Scheme);
+ net::SpdyHeaderBlock::const_iterator host = block.find(
+ spdy2 ? http::kHost : spdy::kSpdy3Host);
+ net::SpdyHeaderBlock::const_iterator path = block.find(
+ spdy2 ? spdy::kSpdy2Url : spdy::kSpdy3Path);
+ net::SpdyHeaderBlock::const_iterator version = block.find(
+ spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version);
+
+ if (method == block.end() ||
+ scheme == block.end() ||
+ host == block.end() ||
+ path == block.end() ||
+ version == block.end()) {
+ return false;
+ }
+
+ visitor->OnRequestLine(method->second, path->second, version->second);
+ return true;
+}
+
+// Convert the given SPDY header into HTTP header(s) by splitting on NUL bytes
+// calling the specified method (either OnLeadingHeader or OnTrailingHeader) of
+// the given visitor.
+template <void(HttpRequestVisitorInterface::*OnHeader)(
+ const base::StringPiece& key, const base::StringPiece& value)>
+void InsertHeader(const base::StringPiece key,
+ const base::StringPiece value,
+ HttpRequestVisitorInterface* visitor) {
+ // Split header values on null characters, emitting a separate
+ // header key-value pair for each substring. Logic from
+ // net/spdy/spdy_session.cc
+ for (size_t start = 0, end = 0; end != value.npos; start = end) {
+ start = value.find_first_not_of('\0', start);
+ if (start == value.npos) {
+ break;
+ }
+ end = value.find('\0', start);
+ (visitor->*OnHeader)(key, (end != value.npos ?
+ value.substr(start, (end - start)) :
+ value.substr(start)));
+ }
+}
+
+} // namespace
+
+SpdyToHttpConverter::SpdyToHttpConverter(spdy::SpdyVersion spdy_version,
+ HttpRequestVisitorInterface* visitor)
+ : spdy_version_(spdy_version),
+ visitor_(visitor),
+ state_(NO_FRAMES_YET),
+ use_chunking_(true),
+ seen_accept_encoding_(false) {
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ CHECK(visitor);
+}
+
+SpdyToHttpConverter::~SpdyToHttpConverter() {}
+
+// static
+const char* SpdyToHttpConverter::StatusString(Status status) {
+ switch (status) {
+ case SPDY_CONVERTER_SUCCESS: return "SPDY_CONVERTER_SUCCESS";
+ case FRAME_BEFORE_SYN_STREAM: return "FRAME_BEFORE_SYN_STREAM";
+ case FRAME_AFTER_FIN: return "FRAME_AFTER_FIN";
+ case EXTRA_SYN_STREAM: return "EXTRA_SYN_STREAM";
+ case INVALID_HEADER_BLOCK: return "INVALID_HEADER_BLOCK";
+ case BAD_REQUEST: return "BAD_REQUEST";
+ default:
+ LOG(DFATAL) << "Invalid status value: " << status;
+ return "???";
+ }
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertSynStreamFrame(
+ const net::SpdySynStreamIR& frame) {
+ if (state_ != NO_FRAMES_YET) {
+ return EXTRA_SYN_STREAM;
+ }
+ state_ = RECEIVED_SYN_STREAM;
+
+ const net::SpdyHeaderBlock& block = frame.name_value_block();
+
+ if (!GenerateRequestLine(spdy_version(), block, visitor_)) {
+ return BAD_REQUEST;
+ }
+
+ // Translate the headers to HTTP.
+ GenerateLeadingHeaders(block);
+
+ // If this is the last (i.e. only) frame on this stream, finish off the HTTP
+ // request.
+ if (frame.fin()) {
+ FinishRequest();
+ }
+
+ return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertHeadersFrame(
+ const net::SpdyHeadersIR& frame) {
+ if (state_ == RECEIVED_FLAG_FIN) {
+ return FRAME_AFTER_FIN;
+ } else if (state_ == NO_FRAMES_YET) {
+ return FRAME_BEFORE_SYN_STREAM;
+ }
+
+ // Parse the headers from the HEADERS frame. If there have already been any
+ // data frames, then we need to save these headers for later and send them as
+ // trailing headers. Otherwise, we can send them immediately.
+ if (state_ == RECEIVED_DATA) {
+ if (use_chunking_) {
+ const net::SpdyHeaderBlock& block = frame.name_value_block();
+ trailing_headers_.insert(block.begin(), block.end());
+ } else {
+ LOG(WARNING) << "Client sent trailing headers, "
+ << "but we had to ignore them.";
+ }
+ } else {
+ DCHECK(state_ == RECEIVED_SYN_STREAM);
+ DCHECK(trailing_headers_.empty());
+ // Translate the headers to HTTP.
+ GenerateLeadingHeaders(frame.name_value_block());
+ }
+
+ // If this is the last frame on this stream, finish off the HTTP request.
+ if (frame.fin()) {
+ FinishRequest();
+ }
+
+ return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertDataFrame(
+ const net::SpdyDataIR& frame) {
+ if (state_ == RECEIVED_FLAG_FIN) {
+ return FRAME_AFTER_FIN;
+ } else if (state_ == NO_FRAMES_YET) {
+ return FRAME_BEFORE_SYN_STREAM;
+ }
+
+ // If this is the first data frame in the stream, we need to close the HTTP
+ // headers section (for streams where there are never any data frames, we
+ // close the headers section in FinishRequest instead). Just before we do,
+ // we may need to add some last-minute headers.
+ if (state_ == RECEIVED_SYN_STREAM) {
+ state_ = RECEIVED_DATA;
+
+ // Unless we're not using chunked encoding (due to having received a
+ // Content-Length headers), set Transfer-Encoding: chunked now.
+ if (use_chunking_) {
+ visitor_->OnLeadingHeader(http::kTransferEncoding, http::kChunked);
+ }
+
+ // Add any other last minute headers we need, and close the leading headers
+ // section.
+ EndOfLeadingHeaders();
+ }
+ DCHECK(state_ == RECEIVED_DATA);
+
+ // Translate the SPDY data frame into an HTTP data chunk. However, we must
+ // not emit a zero-length chunk, as that would be interpreted as the
+ // data-chunks-complete marker.
+ if (frame.data().size() > 0) {
+ if (use_chunking_) {
+ visitor_->OnDataChunk(frame.data());
+ } else {
+ visitor_->OnRawData(frame.data());
+ }
+ }
+
+ // If this is the last frame on this stream, finish off the HTTP request.
+ if (frame.fin()) {
+ FinishRequest();
+ }
+
+ return SPDY_CONVERTER_SUCCESS;
+}
+
+// Convert the given SPDY header block (e.g. from a SYN_STREAM or HEADERS
+// frame) into HTTP headers by calling OnLeadingHeader on the given visitor.
+void SpdyToHttpConverter::GenerateLeadingHeaders(
+ const net::SpdyHeaderBlock& block) {
+ for (net::SpdyHeaderBlock::const_iterator it = block.begin();
+ it != block.end(); ++it) {
+ base::StringPiece key = it->first;
+ const base::StringPiece value = it->second;
+
+ // Skip SPDY-specific (i.e. non-HTTP) headers.
+ if (spdy_version() < spdy::SPDY_VERSION_3) {
+ if (key == spdy::kSpdy2Method || key == spdy::kSpdy2Scheme ||
+ key == spdy::kSpdy2Url || key == spdy::kSpdy2Version) {
+ continue;
+ }
+ } else {
+ if (key == spdy::kSpdy3Method || key == spdy::kSpdy3Scheme ||
+ key == spdy::kSpdy3Path || key == spdy::kSpdy3Version) {
+ continue;
+ }
+ }
+
+ // Skip headers that are ignored by SPDY.
+ if (key == http::kConnection || key == http::kKeepAlive) {
+ continue;
+ }
+
+ // If the client sent a Content-Length header, take note, so that we'll
+ // know not to used chunked encoding.
+ if (key == http::kContentLength) {
+ use_chunking_ = false;
+ }
+
+ // The client shouldn't be sending us a Transfer-Encoding header; it's
+ // pretty pointless over SPDY. If they do send one, just ignore it; we may
+ // be overriding it later anyway.
+ if (key == http::kTransferEncoding) {
+ LOG(WARNING) << "Client sent \"transfer-encoding: " << value
+ << "\" header over SPDY. Why would they do that?";
+ continue;
+ }
+
+ // For SPDY v3 and later, we need to convert the SPDY ":host" header to an
+ // HTTP "host" header.
+ if (spdy_version() >= spdy::SPDY_VERSION_3 && key == spdy::kSpdy3Host) {
+ key = http::kHost;
+ }
+
+ // Take note of whether the client has sent an explicit Accept-Encoding
+ // header; if they never do, we'll insert on for them later on.
+ if (key == http::kAcceptEncoding) {
+ // TODO(mdsteele): Ideally, if the client sends something like
+ // "Accept-Encoding: lzma", we should change it to "Accept-Encoding:
+ // lzma, gzip". However, that's more work (we might need to parse the
+ // syntax, to make sure we don't naively break it), and isn't
+ // (currently) likely to come up in practice.
+ seen_accept_encoding_ = true;
+ }
+
+ InsertHeader<&HttpRequestVisitorInterface::OnLeadingHeader>(
+ key, value, visitor_);
+ }
+}
+
+void SpdyToHttpConverter::EndOfLeadingHeaders() {
+ // All SPDY clients should be assumed to support both gzip and deflate, even
+ // if they don't say so (SPDY draft 2 section 3; SPDY draft 3 section 3.2.1),
+ // and indeed some SPDY clients omit the Accept-Encoding header. So if we
+ // didn't see that header yet, add one now so that Apache knows it can use
+ // gzip/deflate.
+ if (!seen_accept_encoding_) {
+ visitor_->OnLeadingHeader(http::kAcceptEncoding, http::kGzipDeflate);
+ }
+
+ visitor_->OnLeadingHeadersComplete();
+}
+
+void SpdyToHttpConverter::FinishRequest() {
+ if (state_ == RECEIVED_DATA) {
+ if (use_chunking_) {
+ // Indicate that there is no more data coming.
+ visitor_->OnDataChunksComplete();
+
+ // Append whatever trailing headers we've buffered, if any.
+ if (!trailing_headers_.empty()) {
+ for (net::SpdyHeaderBlock::const_iterator it =
+ trailing_headers_.begin();
+ it != trailing_headers_.end(); ++it) {
+ InsertHeader<&HttpRequestVisitorInterface::OnTrailingHeader>(
+ it->first, it->second, visitor_);
+ }
+ trailing_headers_.clear();
+ visitor_->OnTrailingHeadersComplete();
+ }
+ } else {
+ // We don't add to trailing_headers_ if we're in no-chunk mode (we simply
+ // ignore trailing HEADERS frames), so trailing_headers_ should still be
+ // empty.
+ DCHECK(trailing_headers_.empty());
+ }
+ } else {
+ DCHECK(state_ == RECEIVED_SYN_STREAM);
+ // We only ever add to trailing_headers_ after receiving at least one data
+ // frame, so if we haven't received any data frames then trailing_headers_
+ // should still be empty.
+ DCHECK(trailing_headers_.empty());
+
+ // There were no data frames in this stream, so we haven't closed the
+ // normal (non-trailing) headers yet (if there had been any data frames, we
+ // would have closed the normal headers in ConvertDataFrame instead). Do
+ // so now.
+ EndOfLeadingHeaders();
+ }
+
+ // Indicate that this request is finished.
+ visitor_->OnComplete();
+ state_ = RECEIVED_FLAG_FIN;
+}
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2010 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.
+
+#ifndef MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
+#define MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class HttpRequestVisitorInterface;
+
+// Incrementally converts SPDY frames to HTTP streams, and passes the HTTP
+// stream to the specified HttpRequestVisitorInterface.
+class SpdyToHttpConverter {
+ public:
+ SpdyToHttpConverter(spdy::SpdyVersion spdy_version,
+ HttpRequestVisitorInterface* visitor);
+ ~SpdyToHttpConverter();
+
+ enum Status {
+ SPDY_CONVERTER_SUCCESS,
+ FRAME_BEFORE_SYN_STREAM, // first frame was not a SYN_STREAM
+ FRAME_AFTER_FIN, // received another frame after a FLAG_FIN
+ EXTRA_SYN_STREAM, // received an additional SYN_STREAM after the first
+ INVALID_HEADER_BLOCK, // the headers could not be parsed
+ BAD_REQUEST // the headers didn't constitute a valid HTTP request
+ };
+
+ static const char* StatusString(Status status);
+
+ // Return the SPDY version from which we are converting.
+ spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+
+ // Convert the SPDY frame to HTTP and make appropriate calls to the visitor.
+ // In some cases data may be buffered, but everything will get flushed out to
+ // the visitor by the time the final frame (with FLAG_FIN set) is done.
+ Status ConvertSynStreamFrame(const net::SpdySynStreamIR& frame);
+ Status ConvertHeadersFrame(const net::SpdyHeadersIR& frame);
+ Status ConvertDataFrame(const net::SpdyDataIR& frame);
+
+private:
+ // Called to generate leading headers from a SYN_STREAM or HEADERS frame.
+ void GenerateLeadingHeaders(const net::SpdyHeaderBlock& block);
+ // Called when there are no more leading headers, because we've received
+ // either data or a FLAG_FIN. This adds any last-minute needed headers
+ // before closing the leading headers section.
+ void EndOfLeadingHeaders();
+ // Called when we see a FLAG_FIN. This terminates the request and appends
+ // whatever trailing headers (if any) we have buffered.
+ void FinishRequest();
+
+ enum State {
+ NO_FRAMES_YET, // We haven't seen any frames yet.
+ RECEIVED_SYN_STREAM, // We've seen the SYN_STREAM, but no DATA yet.
+ RECEIVED_DATA, // We've seen at least one DATA frame.
+ RECEIVED_FLAG_FIN // We've seen the FLAG_FIN; no more frames allowed.
+ };
+
+ const spdy::SpdyVersion spdy_version_;
+ HttpRequestVisitorInterface* const visitor_;
+ net::SpdyHeaderBlock trailing_headers_;
+ State state_;
+ bool use_chunking_;
+ bool seen_accept_encoding_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyToHttpConverter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
--- /dev/null
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/spdy_to_http_converter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using mod_spdy::SpdyToHttpConverter;
+using testing::Eq;
+using testing::InSequence;
+using testing::Sequence;
+
+const char* kMethod = "GET";
+const char* kScheme = "http";
+const char* kHost = "www.example.com";
+const char* kPath = "/";
+const char* kVersion = "HTTP/1.1";
+const char kMultiValue[] = "this\0is\0\0\0four\0\0headers";
+
+class MockHttpRequestVisitor: public mod_spdy::HttpRequestVisitorInterface {
+ public:
+ MOCK_METHOD3(OnRequestLine, void(const base::StringPiece&,
+ const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD2(OnLeadingHeader, void(const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD0(OnLeadingHeadersComplete, void());
+ MOCK_METHOD1(OnRawData, void(const base::StringPiece&));
+ MOCK_METHOD1(OnDataChunk, void(const base::StringPiece&));
+ MOCK_METHOD0(OnDataChunksComplete, void());
+ MOCK_METHOD2(OnTrailingHeader, void(const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD0(OnTrailingHeadersComplete, void());
+ MOCK_METHOD0(OnComplete, void());
+};
+
+class SpdyToHttpConverterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ SpdyToHttpConverterTest() : converter_(GetParam(), &visitor_) {}
+
+ protected:
+ void AddRequiredHeaders() {
+ if (converter_.spdy_version() < mod_spdy::spdy::SPDY_VERSION_3) {
+ headers_[mod_spdy::spdy::kSpdy2Method] = kMethod;
+ headers_[mod_spdy::spdy::kSpdy2Scheme] = kScheme;
+ headers_[mod_spdy::http::kHost] = kHost;
+ headers_[mod_spdy::spdy::kSpdy2Url] = kPath;
+ headers_[mod_spdy::spdy::kSpdy2Version] = kVersion;
+ } else {
+ headers_[mod_spdy::spdy::kSpdy3Method] = kMethod;
+ headers_[mod_spdy::spdy::kSpdy3Scheme] = kScheme;
+ headers_[mod_spdy::spdy::kSpdy3Host] = kHost;
+ headers_[mod_spdy::spdy::kSpdy3Path] = kPath;
+ headers_[mod_spdy::spdy::kSpdy3Version] = kVersion;
+ }
+ }
+
+ MockHttpRequestVisitor visitor_;
+ SpdyToHttpConverter converter_;
+ net::SpdyHeaderBlock headers_;
+};
+
+TEST_P(SpdyToHttpConverterTest, MultiFrameStream) {
+ // We expect all calls to happen in the specified order.
+ InSequence seq;
+
+ const net::SpdyStreamId stream_id = 1;
+ AddRequiredHeaders();
+
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("transfer-encoding"),
+ Eq("chunked")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate)));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete());
+ scoped_ptr<net::SpdySynStreamIR> syn_stream_frame(
+ new net::SpdySynStreamIR(stream_id));
+ syn_stream_frame->set_priority(1);
+ syn_stream_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_stream_frame));
+
+ EXPECT_CALL(visitor_, OnDataChunk(Eq(kHost)));
+ scoped_ptr<net::SpdyDataIR> data_frame_1(
+ new net::SpdyDataIR(stream_id, kHost));
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame_1));
+
+ // Should be no call to OnDataChunk for an empty data frame.
+ scoped_ptr<net::SpdyDataIR> data_frame_empty(
+ new net::SpdyDataIR(stream_id, ""));
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame_empty));
+
+ EXPECT_CALL(visitor_, OnDataChunk(Eq(kVersion)));
+ EXPECT_CALL(visitor_, OnDataChunksComplete());
+ EXPECT_CALL(visitor_, OnComplete());
+ scoped_ptr<net::SpdyDataIR> data_frame_2(
+ new net::SpdyDataIR(stream_id, kVersion));
+ data_frame_2->set_fin(true);
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame_2));
+}
+
+TEST_P(SpdyToHttpConverterTest, SynFrameWithHeaders) {
+ AddRequiredHeaders();
+ headers_["foo"] = "bar";
+ headers_[mod_spdy::http::kAcceptEncoding] = "deflate, gzip, lzma";
+
+ // Create a multi-valued header to verify that it's processed
+ // properly.
+ std::string multi_values(kMultiValue, sizeof(kMultiValue));
+ headers_["multi"] = multi_values;
+
+ // Also make sure "junk" headers get skipped over.
+ headers_["empty"] = std::string("\0\0\0", 3);
+
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->set_fin(true);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ // We expect a call to OnRequestLine(), followed by several calls to
+ // OnLeadingHeader() (the order of the calls to OnLeadingHeader() is
+ // non-deterministic so we put each in its own Sequence), followed by a final
+ // call to OnLeadingHeadersComplete() and OnComplete().
+ Sequence s1, s2, s3, s4;
+ EXPECT_CALL(visitor_,
+ OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+ .InSequence(s1, s2, s3, s4);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("foo"), Eq("bar")))
+ .InSequence(s1);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq(mod_spdy::http::kAcceptEncoding),
+ Eq("deflate, gzip, lzma")))
+ .InSequence(s2);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("this")))
+ .InSequence(s3);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("is")))
+ .InSequence(s3);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("four")))
+ .InSequence(s3);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("headers")))
+ .InSequence(s3);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+ .InSequence(s4);
+
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2, s3, s4);
+
+ EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2, s3, s4);
+
+ // Trigger the calls to the mock object by passing the frame to the
+ // converter.
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, TrailingHeaders) {
+ // First, send a SYN_STREAM frame without FLAG_FIN set. We should get the
+ // headers out that we sent, but no call yet to OnLeadingHeadersComplete,
+ // because there might still be a HEADERS frame.
+ AddRequiredHeaders();
+ headers_["foo"] = "bar";
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ Sequence s1, s2;
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+ .InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("foo"), Eq("bar")))
+ .InSequence(s1);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+ .InSequence(s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+
+ // Next, send a DATA frame. This should trigger the accept-encoding and
+ // transfer-encoding headers, and the end of the leading headers (along with
+ // the data itself, of course).
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(1, "Hello, world!\n"));
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("transfer-encoding"),
+ Eq("chunked"))).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate))).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnDataChunk(Eq("Hello, world!\n"))).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame));
+
+ // Finally, send a HEADERS frame with FLAG_FIN set. Since this is the end of
+ // the stream, we should get out a trailing header and the HTTP stream should
+ // be closed.
+ headers_.clear();
+ headers_["quux"] = "baz";
+ scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+ headers_frame->set_fin(true);
+ headers_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ EXPECT_CALL(visitor_, OnDataChunksComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnTrailingHeader(Eq("quux"), Eq("baz")))
+ .InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnTrailingHeadersComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, WithContentLength) {
+ // First, send a SYN_STREAM frame without FLAG_FIN set. We should get the
+ // headers out that we sent, but no call yet to OnLeadingHeadersComplete,
+ // because there might still be a HEADERS frame.
+ AddRequiredHeaders();
+ headers_["content-length"] = "11";
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ Sequence s1, s2;
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+ .InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("content-length"), Eq("11")))
+ .InSequence(s1);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+ .InSequence(s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+
+ // Next, send a DATA frame. This should trigger the end of the leading
+ // headers (along with the data itself, of course), but because we sent a
+ // content-length, the data should not be chunked.
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(1, "foobar=quux"));
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate))).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnRawData(Eq("foobar=quux"))).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame));
+
+ // Finally, send a HEADERS frame with FLAG_FIN set. Since we're not chunking
+ // this stream, the trailing headers should be ignored.
+ headers_.clear();
+ headers_["x-metadata"] = "baz";
+ scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+ headers_frame->set_fin(true);
+ headers_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DoubleSynStreamFrame) {
+ AddRequiredHeaders();
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(
+ new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->set_fin(true);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate)));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete());
+ EXPECT_CALL(visitor_, OnComplete());
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+ EXPECT_EQ(SpdyToHttpConverter::EXTRA_SYN_STREAM,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, HeadersFrameBeforeSynStreamFrame) {
+ headers_["x-foo"] = "bar";
+ scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+ headers_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+ EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+ converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DataFrameBeforeSynStreamFrame) {
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(1, kHost));
+ EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+ converter_.ConvertDataFrame(*data_frame));
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, SpdyToHttpConverterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/testing/async_task_runner.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/thread_pool.h"
+#include "net/instaweb/util/public/function.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+namespace {
+
+class TaskFunction : public net_instaweb::Function {
+ public:
+ TaskFunction(AsyncTaskRunner::Task* task, Notification* done)
+ : task_(task), done_(done) {}
+ virtual ~TaskFunction() {}
+ protected:
+ // net_instaweb::Function methods:
+ virtual void Run() {
+ task_->Run();
+ done_->Set();
+ }
+ virtual void Cancel() {}
+ private:
+ AsyncTaskRunner::Task* const task_;
+ Notification* const done_;
+ DISALLOW_COPY_AND_ASSIGN(TaskFunction);
+};
+
+} // namespace
+
+AsyncTaskRunner::Task::Task() {}
+
+AsyncTaskRunner::Task::~Task() {}
+
+AsyncTaskRunner::AsyncTaskRunner(Task* task)
+ : task_(task), thread_pool_(1, 1) {}
+
+AsyncTaskRunner::~AsyncTaskRunner() {}
+
+bool AsyncTaskRunner::Start() {
+ // Make sure we haven't started yet.
+ DCHECK(executor_ == NULL);
+
+ if (!thread_pool_.Start()) {
+ return false;
+ }
+ executor_.reset(thread_pool_.NewExecutor());
+ executor_->AddTask(new TaskFunction(task_.get(), ¬ification_), 0);
+ return true;
+}
+
+} // namespace testing
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
+#define MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/thread_pool.h"
+
+namespace mod_spdy {
+
+class Executor;
+
+namespace testing {
+
+// An AsyncTaskRunner is a testing utility class to make it very easy to run a
+// single piece of code concurrently, in order to write tests for concurrency
+// features of other classes. Standard usage is:
+//
+// class FooTask : public AsyncTaskRunner::Task { ... };
+//
+// AsyncTaskRunner runner(new FooTask(...));
+// ASSERT_TRUE(runner.Start());
+// ... stuff goes here ...
+// runner.notification()->ExpectSetWithin(...); // or whatever
+//
+// Note that the implementation of this class is not particularly efficient,
+// and is suitable only for testing purposes.
+class AsyncTaskRunner {
+ public:
+ // A closure to be run by the AsyncTaskRunner. If we had a simple closure
+ // class available already, we'd use that instead.
+ class Task {
+ public:
+ Task();
+ virtual ~Task();
+ virtual void Run() = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Task);
+ };
+
+ // Construct an AsyncTaskRunner that will run the given task once started.
+ // The AsyncTaskRunner gains ownership of the task.
+ explicit AsyncTaskRunner(Task* task);
+
+ ~AsyncTaskRunner();
+
+ // Start the task running and return true. If this fails, it returns false,
+ // and the test should be aborted.
+ bool Start();
+
+ // Get the notification that will be set when the task completes.
+ Notification* notification() { return ¬ification_; }
+
+ private:
+ const scoped_ptr<Task> task_;
+ mod_spdy::ThreadPool thread_pool_;
+ scoped_ptr<Executor> executor_;
+ Notification notification_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncTaskRunner);
+};
+
+} // namespace testing
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/testing/notification.h"
+
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+Notification::Notification() : condvar_(&lock_), is_set_(false) {}
+
+Notification::~Notification() {
+ Set();
+}
+
+void Notification::Set() {
+ base::AutoLock autolock(lock_);
+ is_set_ = true;
+ condvar_.Broadcast();
+}
+
+void Notification::Wait() {
+ base::AutoLock autolock(lock_);
+ while (!is_set_) {
+ condvar_.Wait();
+ }
+}
+
+void Notification::ExpectNotSet() {
+ base::AutoLock autolock(lock_);
+ EXPECT_FALSE(is_set_);
+}
+
+void Notification::ExpectSetWithin(const base::TimeDelta& timeout) {
+ base::AutoLock autolock(lock_);
+ const base::TimeDelta zero = base::TimeDelta();
+ base::TimeDelta time_remaining = timeout;
+ while (time_remaining > zero && !is_set_) {
+ const base::TimeTicks start = base::TimeTicks::HighResNow();
+ condvar_.TimedWait(time_remaining);
+ time_remaining -= base::TimeTicks::HighResNow() - start;
+ }
+ EXPECT_TRUE(is_set_);
+}
+
+void Notification::ExpectSetWithinMillis(int millis) {
+ ExpectSetWithin(base::TimeDelta::FromMilliseconds(millis));
+}
+
+} // namespace testing
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
+#define MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace base { class TimeDelta; }
+
+namespace mod_spdy {
+
+namespace testing {
+
+// A Notification allows one thread to Wait() until another thread calls Set()
+// at least once. In order to help avoid deadlock, Set() is also called by the
+// destructor.
+class Notification {
+ public:
+ Notification();
+ ~Notification();
+
+ // Set the notification.
+ void Set();
+ // Block until the notification is set.
+ void Wait();
+ // In a unit test, expect that the notification has not yet been set.
+ void ExpectNotSet();
+ // In a unit test, expect that the notification is currently set, or becomes
+ // set by another thread within the give time delta.
+ void ExpectSetWithin(const base::TimeDelta& delay);
+ void ExpectSetWithinMillis(int millis);
+
+ private:
+ base::Lock lock_;
+ base::ConditionVariable condvar_;
+ bool is_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(Notification);
+};
+
+} // namespace testing
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+
+#include <iostream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/stringprintf.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+void AppendHeadersString(const net::SpdyNameValueBlock& headers,
+ std::string* out) {
+ out->append("{ ");
+ bool comma = false;
+ for (net::SpdyNameValueBlock::const_iterator iter = headers.begin();
+ iter != headers.end(); ++iter) {
+ if (comma) {
+ out->append(", ");
+ }
+ base::StringAppendF(out, "'%s': '%s'", iter->first.c_str(),
+ iter->second.c_str());
+ comma = true;
+ }
+ out->append(" }");
+}
+
+class FrameToStringVisitor : public net::SpdyFrameVisitor {
+ public:
+ explicit FrameToStringVisitor(std::string* out)
+ : out_(out) {
+ CHECK(out_);
+ }
+ virtual ~FrameToStringVisitor() {}
+
+ virtual void VisitSynStream(const net::SpdySynStreamIR& syn_stream) {
+ // TODO(mdsteele): include other fields
+ base::StringAppendF(
+ out_, "SYN_STREAM(%u p%u%s%s)",
+ static_cast<unsigned>(syn_stream.stream_id()),
+ static_cast<unsigned>(syn_stream.priority()),
+ (syn_stream.fin() ? " fin" : ""),
+ (syn_stream.unidirectional() ? " unidirectional" : ""));
+ AppendHeadersString(syn_stream.name_value_block(), out_);
+ }
+ virtual void VisitSynReply(const net::SpdySynReplyIR& syn_reply) {
+ base::StringAppendF(
+ out_, "SYN_REPLY(%u%s)",
+ static_cast<unsigned>(syn_reply.stream_id()),
+ (syn_reply.fin() ? " fin" : ""));
+ AppendHeadersString(syn_reply.name_value_block(), out_);
+ }
+ virtual void VisitRstStream(const net::SpdyRstStreamIR& rst_stream) {
+ base::StringAppendF(
+ out_, "RST_STREAM(%u %s)",
+ static_cast<unsigned>(rst_stream.stream_id()),
+ mod_spdy::RstStreamStatusCodeToString(rst_stream.status()));
+ }
+ virtual void VisitSettings(const net::SpdySettingsIR& settings) {
+ base::StringAppendF(
+ out_, "SETTINGS(%s",
+ (settings.clear_settings() ? "clear " : ""));
+ bool comma = false;
+ for (net::SpdySettingsIR::ValueMap::const_iterator iter =
+ settings.values().begin(), end = settings.values().end();
+ iter != end; ++iter) {
+ if (comma) {
+ out_->append(", ");
+ }
+ base::StringAppendF(
+ out_, "%s%s%s: %d",
+ (iter->second.persist_value ? "persist " : ""),
+ (iter->second.persisted ? "persisted " : ""),
+ mod_spdy::SettingsIdToString(iter->first),
+ static_cast<int>(iter->second.value));
+ }
+ out_->append(")");
+ }
+ virtual void VisitPing(const net::SpdyPingIR& ping) {
+ base::StringAppendF(
+ out_, "PING(%u)", static_cast<unsigned>(ping.id()));
+ }
+ virtual void VisitGoAway(const net::SpdyGoAwayIR& goaway) {
+ base::StringAppendF(
+ out_, "GOAWAY(%u %s)",
+ static_cast<unsigned>(goaway.last_good_stream_id()),
+ mod_spdy::GoAwayStatusCodeToString(goaway.status()));
+ }
+ virtual void VisitHeaders(const net::SpdyHeadersIR& headers) {
+ base::StringAppendF(
+ out_, "HEADERS(%u%s)", static_cast<unsigned>(headers.stream_id()),
+ (headers.fin() ? " fin" : ""));
+ AppendHeadersString(headers.name_value_block(), out_);
+ }
+ virtual void VisitWindowUpdate(const net::SpdyWindowUpdateIR& window) {
+ base::StringAppendF(
+ out_, "WINDOW_UPDATE(%u %+d)",
+ static_cast<unsigned>(window.stream_id()),
+ static_cast<int>(window.delta()));
+ }
+ virtual void VisitCredential(const net::SpdyCredentialIR& credential) {
+ // TODO(mdsteele): include other fields
+ base::StringAppendF(
+ out_, "CREDENTIAL(%d)", static_cast<int>(credential.slot()));
+ }
+ virtual void VisitBlocked(const net::SpdyBlockedIR& blocked) {
+ base::StringAppendF(
+ out_, "BLOCKED(%u)", static_cast<unsigned>(blocked.stream_id()));
+ }
+ virtual void VisitPushPromise(const net::SpdyPushPromiseIR& push_promise) {
+ base::StringAppendF(
+ out_, "PUSH_PROMISE(%u, %u)",
+ static_cast<unsigned>(push_promise.stream_id()),
+ static_cast<unsigned>(push_promise.promised_stream_id()));
+ }
+ virtual void VisitData(const net::SpdyDataIR& data) {
+ base::StringAppendF(
+ out_, "DATA(%u%s \"", static_cast<unsigned>(data.stream_id()),
+ (data.fin() ? " fin" : ""));
+ out_->append(data.data().data(), data.data().size());
+ out_->append("\")");
+ }
+
+ private:
+ std::string* out_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameToStringVisitor);
+};
+
+void AppendSpdyFrameToString(const net::SpdyFrameIR& frame, std::string* out) {
+ FrameToStringVisitor visitor(out);
+ frame.Visit(&visitor);
+}
+
+class IsEquivalentFrameMatcher :
+ public ::testing::MatcherInterface<const net::SpdyFrameIR&> {
+ public:
+ explicit IsEquivalentFrameMatcher(const net::SpdyFrameIR& frame);
+ virtual ~IsEquivalentFrameMatcher();
+ virtual bool MatchAndExplain(const net::SpdyFrameIR& frame,
+ ::testing::MatchResultListener* listener) const;
+ virtual void DescribeTo(std::ostream* out) const;
+ virtual void DescribeNegationTo(std::ostream* out) const;
+
+ private:
+ std::string expected_;
+
+ DISALLOW_COPY_AND_ASSIGN(IsEquivalentFrameMatcher);
+};
+
+IsEquivalentFrameMatcher::IsEquivalentFrameMatcher(
+ const net::SpdyFrameIR& frame) {
+ AppendSpdyFrameToString(frame, &expected_);
+}
+
+IsEquivalentFrameMatcher::~IsEquivalentFrameMatcher() {}
+
+bool IsEquivalentFrameMatcher::MatchAndExplain(
+ const net::SpdyFrameIR& frame,
+ ::testing::MatchResultListener* listener) const {
+ std::string actual;
+ AppendSpdyFrameToString(frame, &actual);
+ if (actual != expected_) {
+ *listener << "is a " << actual << " frame";
+ return false;
+ }
+ return true;
+}
+
+void IsEquivalentFrameMatcher::DescribeTo(std::ostream* out) const {
+ *out << "is a " << expected_ << " frame";
+}
+
+void IsEquivalentFrameMatcher::DescribeNegationTo(std::ostream* out) const {
+ *out << "isn't a " << expected_ << " frame";
+}
+
+} // namespace
+
+namespace mod_spdy {
+
+namespace testing {
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSynStream(
+ net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+ net::SpdyPriority priority, bool fin, bool unidirectional,
+ const net::SpdyNameValueBlock& headers) {
+ net::SpdySynStreamIR frame(stream_id);
+ frame.set_associated_to_stream_id(assoc_stream_id);
+ frame.set_priority(priority);
+ frame.set_fin(fin);
+ frame.set_unidirectional(unidirectional);
+ frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSynReply(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers) {
+ net::SpdySynReplyIR frame(stream_id);
+ frame.set_fin(fin);
+ frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsRstStream(
+ net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status) {
+ net::SpdyRstStreamIR frame(stream_id, status);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSettings(
+ net::SpdySettingsIds id, int32 value) {
+ net::SpdySettingsIR frame;
+ frame.AddSetting(id, false, false, value);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsPing(net::SpdyPingId ping_id) {
+ net::SpdyPingIR frame(ping_id);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsGoAway(
+ net::SpdyStreamId last_good_stream_id, net::SpdyGoAwayStatus status) {
+ net::SpdyGoAwayIR frame(last_good_stream_id, status);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsHeaders(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers) {
+ net::SpdyHeadersIR frame(stream_id);
+ frame.set_fin(fin);
+ frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsWindowUpdate(
+ net::SpdyStreamId stream_id, uint32 delta) {
+ net::SpdyWindowUpdateIR frame(stream_id, delta);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsDataFrame(
+ net::SpdyStreamId stream_id, bool fin, base::StringPiece payload) {
+ net::SpdyDataIR frame(stream_id, payload);
+ frame.set_fin(fin);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+} // namespace testing
+
+} // namespace mod_spdy
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
+#define MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+// Make a matcher that requires the argument to be a SYN_STREAM frame with the
+// given stream ID, associated stream ID, priority, flag_fin,
+// flag_unidirectional, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsSynStream(
+ net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+ net::SpdyPriority priority, bool fin, bool unidirectional,
+ const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a SYN_REPLY frame with the
+// given stream ID, flag_fin, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsSynReply(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a RST_STREAM frame with the
+// given stream ID and status code.
+::testing::Matcher<const net::SpdyFrameIR&> IsRstStream(
+ net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status);
+
+// Make a matcher that requires the argument to be a SETTINGS frame with the
+// given setting.
+::testing::Matcher<const net::SpdyFrameIR&> IsSettings(
+ net::SpdySettingsIds id, int32 value);
+
+// Make a matcher that requires the argument to be a PING frame with the
+// given ID.
+::testing::Matcher<const net::SpdyFrameIR&> IsPing(net::SpdyPingId ping_id);
+
+// Make a matcher that requires the argument to be a GOAWAY frame with the
+// given last-good-stream-ID and status code.
+::testing::Matcher<const net::SpdyFrameIR&> IsGoAway(
+ net::SpdyStreamId last_good_stream_id, net::SpdyGoAwayStatus status);
+
+// Make a matcher that requires the argument to be a HEADERS frame with the
+// given stream ID, flag_fin, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsHeaders(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a WINDOW_UPDATE frame with
+// the given window-size-delta.
+::testing::Matcher<const net::SpdyFrameIR&> IsWindowUpdate(
+ net::SpdyStreamId stream_id, uint32 delta);
+
+// Make a matcher that requires the argument to be a DATA frame.
+::testing::Matcher<const net::SpdyFrameIR&> IsDataFrame(
+ net::SpdyStreamId stream_id, bool fin, base::StringPiece payload);
+
+} // namespace testing
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/common/thread_pool.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/executor.h"
+#include "net/instaweb/util/public/function.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// Shut down a worker thread after it has been idle for this many seconds:
+const int64 kDefaultMaxWorkerIdleSeconds = 60;
+
+} // namespace
+
+namespace mod_spdy {
+
+// An executor that uses the ThreadPool to execute tasks. Returned by
+// ThreadPool::NewExecutor.
+class ThreadPool::ThreadPoolExecutor : public Executor {
+ public:
+ explicit ThreadPoolExecutor(ThreadPool* master)
+ : master_(master),
+ stopping_condvar_(&master_->lock_),
+ stopped_(false) {}
+ virtual ~ThreadPoolExecutor() { Stop(); }
+
+ // Executor methods:
+ virtual void AddTask(net_instaweb::Function* task,
+ net::SpdyPriority priority);
+ virtual void Stop();
+
+ private:
+ friend class ThreadPool;
+ ThreadPool* const master_;
+ base::ConditionVariable stopping_condvar_;
+ bool stopped_; // protected by master_->lock_
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadPoolExecutor);
+};
+
+// Add a task to the executor; if the executor has already been stopped, just
+// cancel the task immediately.
+void ThreadPool::ThreadPoolExecutor::AddTask(net_instaweb::Function* task,
+ net::SpdyPriority priority) {
+ {
+ base::AutoLock autolock(master_->lock_);
+
+ // Clean up any zombie WorkerThreads in the ThreadPool that are waiting for
+ // reaping. If the OS process we're in accumulates too many unjoined
+ // zombie threads over time, the OS might not be able to spawn a new thread
+ // below. So right now is a good time to clean them up.
+ if (!master_->zombies_.empty()) {
+ std::set<WorkerThread*> zombies;
+ zombies.swap(master_->zombies_);
+ // Joining these threads should be basically instant, since they've
+ // already terminated. But to be safe, let's unlock while we join them.
+ base::AutoUnlock autounlock(master_->lock_);
+ ThreadPool::JoinThreads(zombies);
+ }
+
+ // The thread pool shouldn't be shutting down until all executors are
+ // destroyed. Since this executor clearly still exists, the thread pool
+ // must still be open.
+ DCHECK(!master_->shutting_down_);
+
+ // If the executor hasn't been stopped, add the task to the queue and
+ // notify a worker that there's a new task ready to be taken.
+ if (!stopped_) {
+ master_->task_queue_.insert(std::make_pair(priority, Task(task, this)));
+ master_->worker_condvar_.Signal();
+ master_->StartNewWorkerIfNeeded();
+ return;
+ }
+ }
+
+ // If this executor has already been stopped, just cancel the task (after
+ // releasing the lock).
+ task->CallCancel();
+}
+
+// Stop the executor. Cancel all pending tasks in the thread pool owned by
+// this executor, and then block until all active tasks owned by this executor
+// complete. Stopping the executor more than once has no effect.
+void ThreadPool::ThreadPoolExecutor::Stop() {
+ std::vector<net_instaweb::Function*> functions_to_cancel;
+ {
+ base::AutoLock autolock(master_->lock_);
+ if (stopped_) {
+ return;
+ }
+ stopped_ = true;
+
+ // Remove all tasks owned by this executor from the queue, and collect up
+ // the function objects to be cancelled.
+ TaskQueue::iterator next_iter = master_->task_queue_.begin();
+ while (next_iter != master_->task_queue_.end()) {
+ TaskQueue::iterator iter = next_iter;
+ const Task& task = iter->second;
+ ++next_iter; // Increment next_iter _before_ we might erase iter.
+ if (task.owner == this) {
+ functions_to_cancel.push_back(task.function);
+ master_->task_queue_.erase(iter);
+ }
+ }
+ }
+
+ // Unlock while we cancel the functions, so we're not hogging the lock for
+ // too long, and to avoid potential deadlock if the cancel method tries to do
+ // anything with the thread pool.
+ for (std::vector<net_instaweb::Function*>::const_iterator iter =
+ functions_to_cancel.begin();
+ iter != functions_to_cancel.end(); ++iter) {
+ (*iter)->CallCancel();
+ }
+ // CallCancel deletes the Function objects, invalidating the pointers in this
+ // list, so let's go ahead and clear it (which also saves a little memory
+ // while we're blocked below).
+ functions_to_cancel.clear();
+
+ // Block until all our active tasks are completed.
+ {
+ base::AutoLock autolock(master_->lock_);
+ while (master_->active_task_counts_.count(this) > 0) {
+ stopping_condvar_.Wait();
+ }
+ }
+}
+
+// A WorkerThread object wraps a platform-specific thread handle, and provides
+// the method run by that thread (ThreadMain).
+class ThreadPool::WorkerThread : public base::PlatformThread::Delegate {
+ public:
+ explicit WorkerThread(ThreadPool* master);
+ virtual ~WorkerThread();
+
+ // Start the thread running. Return false on failure. If this succeeds,
+ // then you must call Join() before deleting this object.
+ bool Start();
+
+ // Block until the thread completes. You must set master_->shutting_down_ to
+ // true before calling this method, or the thread will never terminate.
+ // You shouldn't be holding master_->lock_ when calling this.
+ void Join();
+
+ // base::PlatformThread::Delegate method:
+ virtual void ThreadMain();
+
+ private:
+ enum ThreadState { NOT_STARTED, STARTED, JOINED };
+
+ ThreadPool* const master_;
+ // If two master threads are sharing the same ThreadPool, then Start() and
+ // Join() might get called by different threads. So to be safe we use a lock
+ // to protect the two below fields.
+ base::Lock thread_lock_;
+ ThreadState state_;
+ base::PlatformThreadHandle thread_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerThread);
+};
+
+ThreadPool::WorkerThread::WorkerThread(ThreadPool* master)
+ : master_(master), state_(NOT_STARTED), thread_id_() {}
+
+ThreadPool::WorkerThread::~WorkerThread() {
+ base::AutoLock autolock(thread_lock_);
+ // If we started the thread, we _must_ join it before deleting this object,
+ // or else the thread won't get cleaned up by the OS.
+ DCHECK(state_ == NOT_STARTED || state_ == JOINED);
+}
+
+bool ThreadPool::WorkerThread::Start() {
+ base::AutoLock autolock(thread_lock_);
+ DCHECK_EQ(NOT_STARTED, state_);
+ if (base::PlatformThread::Create(0, this, &thread_id_)) {
+ state_ = STARTED;
+ return true;
+ }
+ return false;
+}
+
+void ThreadPool::WorkerThread::Join() {
+ base::AutoLock autolock(thread_lock_);
+ DCHECK_EQ(STARTED, state_);
+ base::PlatformThread::Join(thread_id_);
+ state_ = JOINED;
+}
+
+// This is the code executed by the thread; when this method returns, the
+// thread will terminate.
+void ThreadPool::WorkerThread::ThreadMain() {
+ // We start by grabbing the master lock, but we release it below whenever we
+ // are 1) waiting for a new task or 2) executing a task. So in fact most of
+ // the time we are not holding the lock.
+ base::AutoLock autolock(master_->lock_);
+ while (true) {
+ // Wait until there's a task available (or we're shutting down), but don't
+ // stay idle for more than kMaxWorkerIdleSeconds seconds.
+ base::TimeDelta time_remaining = master_->max_thread_idle_time_;
+ while (!master_->shutting_down_ && master_->task_queue_.empty() &&
+ time_remaining.InSecondsF() > 0.0) {
+ // Note that TimedWait can wake up spuriously before the time runs out,
+ // so we need to measure how long we actually waited for.
+ const base::Time start = base::Time::Now();
+ master_->worker_condvar_.TimedWait(time_remaining);
+ const base::Time end = base::Time::Now();
+ // Note that the system clock can go backwards if it is reset, so make
+ // sure we never _increase_ time_remaining.
+ if (end > start) {
+ time_remaining -= end - start;
+ }
+ }
+
+ // If the thread pool is shutting down, terminate this thread; the master
+ // is about to join/delete us (in its destructor).
+ if (master_->shutting_down_) {
+ return;
+ }
+
+ // If we ran out of time without getting a task, maybe this thread should
+ // shut itself down.
+ if (master_->task_queue_.empty()) {
+ DCHECK_LE(time_remaining.InSecondsF(), 0.0);
+ // Ask the master if we should stop. If this returns true, this worker
+ // has been zombified, so we're free to terminate the thread.
+ if (master_->TryZombifyIdleThread(this)) {
+ return; // Yes, we should stop; terminate the thread.
+ } else {
+ continue; // No, we shouldn't stop; jump to the top of the while loop.
+ }
+ }
+
+ // Otherwise, there must be at least one task available now. Grab one from
+ // the master, who will then treat us as busy until we complete it.
+ const Task task = master_->GetNextTask();
+ // Release the lock while we execute the task. Note that we use AutoUnlock
+ // here rather than one AutoLock for the above code and another for the
+ // below code, so that we don't have to release and reacquire the lock at
+ // the edge of the while-loop.
+ {
+ base::AutoUnlock autounlock(master_->lock_);
+ task.function->CallRun();
+ }
+ // Inform the master we have completed the task and are no longer busy.
+ master_->OnTaskComplete(task);
+ }
+}
+
+ThreadPool::ThreadPool(int min_threads, int max_threads)
+ : min_threads_(min_threads),
+ max_threads_(max_threads),
+ max_thread_idle_time_(
+ base::TimeDelta::FromSeconds(kDefaultMaxWorkerIdleSeconds)),
+ worker_condvar_(&lock_),
+ num_busy_workers_(0),
+ shutting_down_(false) {
+ DCHECK_GE(max_thread_idle_time_.InSecondsF(), 0.0);
+ // Note that we check e.g. min_threads rather than min_threads_ (which is
+ // unsigned), in order to catch negative numbers.
+ DCHECK_GE(min_threads, 1);
+ DCHECK_GE(max_threads, 1);
+ DCHECK_LE(min_threads_, max_threads_);
+}
+
+ThreadPool::ThreadPool(int min_threads, int max_threads,
+ base::TimeDelta max_thread_idle_time)
+ : min_threads_(min_threads),
+ max_threads_(max_threads),
+ max_thread_idle_time_(max_thread_idle_time),
+ worker_condvar_(&lock_),
+ num_busy_workers_(0),
+ shutting_down_(false) {
+ DCHECK_GE(max_thread_idle_time_.InSecondsF(), 0.0);
+ DCHECK_GE(min_threads, 1);
+ DCHECK_GE(max_threads, 1);
+ DCHECK_LE(min_threads_, max_threads_);
+}
+
+ThreadPool::~ThreadPool() {
+ base::AutoLock autolock(lock_);
+
+ // If we're doing things right, all the Executors should have been
+ // destroyed before the ThreadPool is destroyed, so there should be no
+ // pending or active tasks.
+ DCHECK(task_queue_.empty());
+ DCHECK(active_task_counts_.empty());
+
+ // Wake up all the worker threads and tell them to shut down.
+ shutting_down_ = true;
+ worker_condvar_.Broadcast();
+
+ // Clean up all our threads.
+ std::set<WorkerThread*> threads;
+ zombies_.swap(threads);
+ threads.insert(workers_.begin(), workers_.end());
+ workers_.clear();
+ {
+ base::AutoUnlock autounlock(lock_);
+ JoinThreads(threads);
+ }
+
+ // Because we had shutting_down_ set to true, nothing should have been added
+ // to our WorkerThread sets while we were unlocked. So we should be all
+ // cleaned up now.
+ DCHECK(workers_.empty());
+ DCHECK(zombies_.empty());
+ DCHECK(task_queue_.empty());
+ DCHECK(active_task_counts_.empty());
+}
+
+bool ThreadPool::Start() {
+ base::AutoLock autolock(lock_);
+ DCHECK(task_queue_.empty());
+ DCHECK(workers_.empty());
+ // Start up min_threads_ workers; if any of the worker threads fail to start,
+ // then this method fails and the ThreadPool should be deleted.
+ for (unsigned int i = 0; i < min_threads_; ++i) {
+ scoped_ptr<WorkerThread> worker(new WorkerThread(this));
+ if (!worker->Start()) {
+ return false;
+ }
+ workers_.insert(worker.release());
+ }
+ DCHECK_EQ(min_threads_, workers_.size());
+ return true;
+}
+
+Executor* ThreadPool::NewExecutor() {
+ return new ThreadPoolExecutor(this);
+}
+
+int ThreadPool::GetNumWorkersForTest() {
+ base::AutoLock autolock(lock_);
+ return workers_.size();
+}
+
+int ThreadPool::GetNumIdleWorkersForTest() {
+ base::AutoLock autolock(lock_);
+ DCHECK_GE(num_busy_workers_, 0u);
+ DCHECK_LE(num_busy_workers_, workers_.size());
+ return workers_.size() - num_busy_workers_;
+}
+
+int ThreadPool::GetNumZombiesForTest() {
+ base::AutoLock autolock(lock_);
+ return zombies_.size();
+}
+
+// This method is called each time we add a new task to the thread pool.
+void ThreadPool::StartNewWorkerIfNeeded() {
+ lock_.AssertAcquired();
+ DCHECK_GE(num_busy_workers_, 0u);
+ DCHECK_LE(num_busy_workers_, workers_.size());
+ DCHECK_GE(workers_.size(), min_threads_);
+ DCHECK_LE(workers_.size(), max_threads_);
+
+ // We create a new worker to handle the task _unless_ either 1) we're already
+ // at the maximum number of threads, or 2) there are already enough idle
+ // workers sitting around to take on this task (and all other pending tasks
+ // that the idle workers haven't yet had a chance to pick up).
+ if (workers_.size() >= max_threads_ ||
+ task_queue_.size() <= workers_.size() - num_busy_workers_) {
+ return;
+ }
+
+ scoped_ptr<WorkerThread> worker(new WorkerThread(this));
+ if (worker->Start()) {
+ workers_.insert(worker.release());
+ } else {
+ LOG(ERROR) << "Failed to start new worker thread.";
+ }
+}
+
+// static
+void ThreadPool::JoinThreads(const std::set<WorkerThread*>& threads) {
+ for (std::set<WorkerThread*>::const_iterator iter = threads.begin();
+ iter != threads.end(); ++iter) {
+ WorkerThread* thread = *iter;
+ thread->Join();
+ delete thread;
+ }
+}
+
+// Call when the worker thread has been idle for a while. Either return false
+// (worker should continue waiting for tasks), or zombify the worker and return
+// true (worker thread should immediately terminate).
+bool ThreadPool::TryZombifyIdleThread(WorkerThread* thread) {
+ lock_.AssertAcquired();
+
+ // Don't terminate the thread if the thread pool is already at the minimum
+ // number of threads.
+ DCHECK_GE(workers_.size(), min_threads_);
+ if (workers_.size() <= min_threads_) {
+ return false;
+ }
+
+ // Remove this thread from the worker set.
+ DCHECK_EQ(1u, workers_.count(thread));
+ workers_.erase(thread);
+
+ // When a (joinable) thread terminates, it must still be cleaned up, either
+ // by another thread joining it, or by detatching it. However, the thread
+ // pool's not shutting down here, so the master thread doesn't know to join
+ // this thread that we're in now, and the Chromium thread abstraction we're
+ // using doesn't currently allow us to detach a thread. So instead, we place
+ // this WorkerThread object into a "zombie" set, which the master thread can
+ // reap later on. Threads that have terminated but that haven't been joined
+ // yet use up only a small amount of memory (I think), so it's okay if we
+ // don't reap it right away, as long as we don't try to spawn new threads
+ // while there's still lots of zombies.
+ DCHECK(!shutting_down_);
+ DCHECK_EQ(0u, zombies_.count(thread));
+ zombies_.insert(thread);
+ return true;
+}
+
+// Get and return the next task from the queue (which must be non-empty), and
+// update our various counters to indicate that the calling worker is busy
+// executing this task.
+ThreadPool::Task ThreadPool::GetNextTask() {
+ lock_.AssertAcquired();
+
+ // Pop the highest-priority task from the queue. Note that smaller values
+ // correspond to higher priorities (SPDY draft 3 section 2.3.3), so
+ // task_queue_.begin() gets us the highest-priority pending task.
+ DCHECK(!task_queue_.empty());
+ TaskQueue::iterator task_iter = task_queue_.begin();
+ const Task task = task_iter->second;
+ task_queue_.erase(task_iter);
+
+ // Increment the count of active tasks for the executor that owns this
+ // task; we'll decrement it again when the task completes.
+ ++(active_task_counts_[task.owner]);
+
+ // The worker that takes this task will be busy until it completes it.
+ DCHECK_LT(num_busy_workers_, workers_.size());
+ ++num_busy_workers_;
+
+ return task;
+}
+
+// Call to indicate that the task has been completed; update our various
+// counters to indicate that the calling worker is no longer busy executing
+// this task.
+void ThreadPool::OnTaskComplete(Task task) {
+ lock_.AssertAcquired();
+
+ // The worker that just finished this task is no longer busy.
+ DCHECK_GE(num_busy_workers_, 1u);
+ --num_busy_workers_;
+
+ // We've completed the task and reaquired the lock, so decrement the count
+ // of active tasks for this owner.
+ OwnerMap::iterator count_iter = active_task_counts_.find(task.owner);
+ DCHECK(count_iter != active_task_counts_.end());
+ DCHECK(count_iter->second > 0);
+ --(count_iter->second);
+
+ // If this was the last active task for the owner, notify anyone who might be
+ // waiting for the owner to stop.
+ if (count_iter->second == 0) {
+ active_task_counts_.erase(count_iter);
+ task.owner->stopping_condvar_.Broadcast();
+ }
+}
+
+} // namespace mod_spdy
--- /dev/null
+// 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.
+
+#ifndef MOD_SPDY_COMMON_THREAD_POOL_H_
+#define MOD_SPDY_COMMON_THREAD_POOL_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/spdy/spdy_protocol.h" // for net::SpdyPriority
+
+namespace net_instaweb { class Function; }
+
+namespace mod_spdy {
+
+class Executor;
+
+// A ThreadPool keeps a pool of threads waiting to perform tasks. One can
+// create any number of Executor objects, using the NewExecutor method, which
+// will all share the threads for executing tasks. If more tasks are queued
+// than there are threads in the pool, these executors will respect task
+// priorities when deciding which tasks to execute first.
+class ThreadPool {
+ public:
+ // Create a new thread pool that uses at least min_threads threads, and at
+ // most max_threads threads, at a time. min_threads must be no greater than
+ // max_threads, and both must be positive.
+ ThreadPool(int min_threads, int max_threads);
+
+ // As above, but specify the amount of time after which to kill idle threads,
+ // rather than using the default value (this is primarily for testing).
+ // max_thread_idle_time must be non-negative.
+ ThreadPool(int min_threads, int max_threads,
+ base::TimeDelta max_thread_idle_time);
+
+ // The destructor will block until all threads in the pool have shut down.
+ // The ThreadPool must not be destroyed until all Executor objects returned
+ // from the NewExecutor method have first been deleted.
+ ~ThreadPool();
+
+ // Start up the thread pool. Must be called exactly one before using the
+ // thread pool; returns true on success, or false on failure. If startup
+ // fails, the ThreadPool must be immediately deleted.
+ bool Start();
+
+ // Return a new Executor object that uses this thread pool to perform tasks.
+ // The caller gains ownership of the returned Executor, and the ThreadPool
+ // must outlive the returned Executor.
+ Executor* NewExecutor();
+
+ // Return the current total number of worker threads. This is provided for
+ // testing purposes only.
+ int GetNumWorkersForTest();
+ // Return the number of worker threads currently idle. This is provided for
+ // testing purposes only.
+ int GetNumIdleWorkersForTest();
+ // Return the number of terminated (zombie) threads that have yet to be
+ // reaped. This is provided for testing purposes only.
+ int GetNumZombiesForTest();
+
+ private:
+ class ThreadPoolExecutor;
+ class WorkerThread;
+
+ // A Task is a simple pair of the Function to run, and the executor to which
+ // the task was added.
+ struct Task {
+ Task(net_instaweb::Function* fun, ThreadPoolExecutor* own)
+ : function(fun), owner(own) {}
+ net_instaweb::Function* function;
+ ThreadPoolExecutor* owner;
+ };
+
+ typedef std::multimap<net::SpdyPriority, Task> TaskQueue;
+ typedef std::map<const ThreadPoolExecutor*, int> OwnerMap;
+
+ // Start a new worker thread if 1) the task queue is larger than the number
+ // of currently idle workers, and 2) we have fewer than the maximum number of
+ // workers. Otherwise, do nothing. Must be holding lock_ when calling this.
+ void StartNewWorkerIfNeeded();
+
+ // Join and delete all worker threads in the given set. This will block
+ // until all the threads have terminated and been cleaned up, so don't call
+ // this while holding the lock_.
+ static void JoinThreads(const std::set<WorkerThread*>& threads);
+
+ // These calls are used to implement the WorkerThread's main function. Must
+ // be holding lock_ when calling any of these.
+ bool TryZombifyIdleThread(WorkerThread* thread);
+ Task GetNextTask();
+ void OnTaskComplete(Task task);
+
+ // The min and max number of threads passed to the constructor. Although the
+ // constructor takes signed ints (for convenience), we store these unsigned
+ // to avoid the need for static_casts when comparing against workers_.size().
+ const unsigned int min_threads_;
+ const unsigned int max_threads_;
+ const base::TimeDelta max_thread_idle_time_;
+ // This single master lock protects all of the below fields, as well as any
+ // mutable data and condition variables in the worker threads and executors.
+ // Having just one lock makes everything much easier to understand.
+ base::Lock lock_;
+ // Workers wait on this condvar when waiting for a new task. We signal it
+ // when a new task becomes available, or when we need to shut down.
+ base::ConditionVariable worker_condvar_;
+ // The list of running worker threads. We keep this around so that we can
+ // join the threads on shutdown.
+ std::set<WorkerThread*> workers_;
+ // Worker threads that have shut themselves down (due to being idle), and are
+ // awaiting cleanup by the master thread.
+ std::set<WorkerThread*> zombies_;
+ // How many workers do we have that are actually executing tasks?
+ unsigned int num_busy_workers_;
+ // We set this to true to tell the worker threads to terminate.
+ bool shutting_down_;
+ // The priority queue of pending tasks. Invariant: all Function objects in
+ // the queue have neither been started nor cancelled yet.
+ TaskQueue task_queue_;
+ // This maps executors to the number of currently running tasks for that
+ // executor; we increment when we start a task, and decrement when we finish
+ // it. If the number is zero, we remove the entry from the map; thus, as an
+ // invariant the map only contains entries for executors with active tasks.
+ OwnerMap active_task_counts_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadPool);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_THREAD_POOL_H_
--- /dev/null
+// 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.
+
+#include "mod_spdy/common/thread_pool.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "net/instaweb/util/public/function.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// When adding tests here, try to keep them robust against thread scheduling
+// differences from run to run. In particular, they shouldn't fail just
+// because you're running under Valgrind.
+
+namespace {
+
+// When run, a TestFunction waits for `wait` millis, then sets `*result` to
+// RAN. When cancelled, it sets *result to CANCELLED.
+class TestFunction : public net_instaweb::Function {
+ public:
+ enum Result { NOTHING, RAN, CANCELLED };
+ TestFunction(int wait, base::Lock* lock, Result* result)
+ : wait_(wait), lock_(lock), result_(result) {}
+ virtual ~TestFunction() {}
+ protected:
+ // net_instaweb::Function methods:
+ virtual void Run() {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(wait_));
+ base::AutoLock autolock(*lock_);
+ *result_ = RAN;
+ }
+ virtual void Cancel() {
+ base::AutoLock autolock(*lock_);
+ *result_ = CANCELLED;
+ }
+ private:
+ const int wait_;
+ base::Lock* const lock_;
+ Result* const result_;
+ DISALLOW_COPY_AND_ASSIGN(TestFunction);
+};
+
+// Test that we execute tasks concurrently, that that we respect priorities
+// when pulling tasks from the queue.
+TEST(ThreadPoolTest, ConcurrencyAndPrioritization) {
+ // Create a thread pool with 2 threads, and an executor.
+ mod_spdy::ThreadPool thread_pool(2, 2);
+ ASSERT_TRUE(thread_pool.Start());
+ scoped_ptr<mod_spdy::Executor> executor(thread_pool.NewExecutor());
+
+ base::Lock lock;
+ TestFunction::Result result0 = TestFunction::NOTHING;
+ TestFunction::Result result1 = TestFunction::NOTHING;
+ TestFunction::Result result2 = TestFunction::NOTHING;
+ TestFunction::Result result3 = TestFunction::NOTHING;
+
+ // Create a high-priority TestFunction, which waits for 200 millis then
+ // records that it ran.
+ executor->AddTask(new TestFunction(200, &lock, &result0), 0);
+ // Create several TestFunctions at different priorities. Each waits 100
+ // millis then records that it ran.
+ executor->AddTask(new TestFunction(100, &lock, &result1), 1);
+ executor->AddTask(new TestFunction(100, &lock, &result3), 3);
+ executor->AddTask(new TestFunction(100, &lock, &result2), 2);
+
+ // Wait 150 millis, then stop the executor.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(150));
+ executor->Stop();
+
+ // Only TestFunctions that _started_ within the first 150 millis should have
+ // run; the others should have been cancelled.
+ // - The priority-0 function should have started first, on the first
+ // thread. It finishes after 200 millis.
+ // - The priority-1 function should run on the second thread. It finishes
+ // after 100 millis.
+ // - The priority-2 function should run on the second thread after the
+ // priority-1 function finishes, even though it was pushed last, because
+ // it's higher-priority than the priority-3 function. It finishes at the
+ // 200-milli mark.
+ // - The priority-3 function should not get a chance to run, because we
+ // stop the executor after 150 millis, and the soonest it could start is
+ // the 200-milli mark.
+ base::AutoLock autolock(lock);
+ EXPECT_EQ(TestFunction::RAN, result0);
+ EXPECT_EQ(TestFunction::RAN, result1);
+ EXPECT_EQ(TestFunction::RAN, result2);
+ EXPECT_EQ(TestFunction::CANCELLED, result3);
+}
+
+// Test that stopping one executor doesn't affect tasks on another executor
+// from the same ThreadPool.
+TEST(ThreadPoolTest, MultipleExecutors) {
+ // Create a thread pool with 3 threads, and two executors.
+ mod_spdy::ThreadPool thread_pool(3, 3);
+ ASSERT_TRUE(thread_pool.Start());
+ scoped_ptr<mod_spdy::Executor> executor1(thread_pool.NewExecutor());
+ scoped_ptr<mod_spdy::Executor> executor2(thread_pool.NewExecutor());
+
+ base::Lock lock;
+ TestFunction::Result e1r1 = TestFunction::NOTHING;
+ TestFunction::Result e1r2 = TestFunction::NOTHING;
+ TestFunction::Result e1r3 = TestFunction::NOTHING;
+ TestFunction::Result e2r1 = TestFunction::NOTHING;
+ TestFunction::Result e2r2 = TestFunction::NOTHING;
+ TestFunction::Result e2r3 = TestFunction::NOTHING;
+
+ // Add some tasks to the executors. Each one takes 50 millis to run.
+ executor1->AddTask(new TestFunction(50, &lock, &e1r1), 0);
+ executor2->AddTask(new TestFunction(50, &lock, &e2r1), 0);
+ executor1->AddTask(new TestFunction(50, &lock, &e1r2), 0);
+ executor2->AddTask(new TestFunction(50, &lock, &e2r2), 1);
+ executor1->AddTask(new TestFunction(50, &lock, &e1r3), 3);
+ executor2->AddTask(new TestFunction(50, &lock, &e2r3), 1);
+
+ // Wait 20 millis (to make sure the first few tasks got picked up), then
+ // destroy executor2, which should stop it. Finally, sleep another 100
+ // millis to give the remaining tasks a chance to finish.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+ executor2.reset();
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+
+ // The three high priority tasks should have all run. The other two tasks on
+ // executor2 should have been cancelled when we stopped executor2, but the
+ // low-priority task on executor1 should have been left untouched, and
+ // allowed to finish.
+ base::AutoLock autolock(lock);
+ EXPECT_EQ(TestFunction::RAN, e1r1);
+ EXPECT_EQ(TestFunction::RAN, e2r1);
+ EXPECT_EQ(TestFunction::RAN, e1r2);
+ EXPECT_EQ(TestFunction::CANCELLED, e2r2);
+ EXPECT_EQ(TestFunction::RAN, e1r3);
+ EXPECT_EQ(TestFunction::CANCELLED, e2r3);
+}
+
+// When run, a WaitFunction blocks until the notification is set.
+class WaitFunction : public net_instaweb::Function {
+ public:
+ WaitFunction(mod_spdy::testing::Notification* notification)
+ : notification_(notification) {}
+ virtual ~WaitFunction() {}
+ protected:
+ // net_instaweb::Function methods:
+ virtual void Run() {
+ notification_->Wait();
+ }
+ virtual void Cancel() {}
+ private:
+ mod_spdy::testing::Notification* const notification_;
+ DISALLOW_COPY_AND_ASSIGN(WaitFunction);
+};
+
+// When run, an IdFunction pushes its ID onto the vector.
+class IdFunction : public net_instaweb::Function {
+ public:
+ IdFunction(int id, base::Lock* lock, base::ConditionVariable* condvar,
+ std::vector<int>* output)
+ : id_(id), lock_(lock), condvar_(condvar), output_(output) {}
+ virtual ~IdFunction() {}
+ protected:
+ // net_instaweb::Function methods:
+ virtual void Run() {
+ base::AutoLock autolock(*lock_);
+ output_->push_back(id_);
+ condvar_->Broadcast();
+ }
+ virtual void Cancel() {}
+ private:
+ const int id_;
+ base::Lock* const lock_;
+ base::ConditionVariable* const condvar_;
+ std::vector<int>* const output_;
+ DISALLOW_COPY_AND_ASSIGN(IdFunction);
+};
+
+// Test that if many tasks of the same priority are added, they are run in the
+// order they were added.
+TEST(ThreadPoolTest, SamePriorityTasksAreFIFO) {
+ // Create a thread pool with just one thread, and an executor.
+ mod_spdy::ThreadPool thread_pool(1, 1);
+ ASSERT_TRUE(thread_pool.Start());
+ scoped_ptr<mod_spdy::Executor> executor(thread_pool.NewExecutor());
+
+ // First, make sure no other tasks will get started until we set the
+ // notification.
+ mod_spdy::testing::Notification start;
+ executor->AddTask(new WaitFunction(&start), 0);
+
+ // Add many tasks to the executor, of varying priorities.
+ const int num_tasks_each_priority = 1000;
+ const int total_num_tasks = 3 * num_tasks_each_priority;
+ base::Lock lock;
+ base::ConditionVariable condvar(&lock);
+ std::vector<int> ids; // protected by lock
+ for (int id = 0; id < num_tasks_each_priority; ++id) {
+ executor->AddTask(new IdFunction(id, &lock, &condvar, &ids), 1);
+ executor->AddTask(new IdFunction(id + num_tasks_each_priority,
+ &lock, &condvar, &ids), 2);
+ executor->AddTask(new IdFunction(id + 2 * num_tasks_each_priority,
+ &lock, &condvar, &ids), 3);
+ }
+
+ // Start us off, then wait for all tasks to finish.
+ start.Set();
+ base::AutoLock autolock(lock);
+ while (static_cast<int>(ids.size()) < total_num_tasks) {
+ condvar.Wait();
+ }
+
+ // Check that the tasks were executed in order by the one worker thread.
+ for (int index = 0; index < total_num_tasks; ++index) {
+ ASSERT_EQ(index, ids[index])
+ << "Task " << ids[index] << " finished in position " << index;
+ }
+}
+
+// Add a test failure if the thread pool does not stabilize to the expected
+// total/idle number of worker threads withing the given timeout.
+void ExpectWorkersWithinTimeout(int expected_num_workers,
+ int expected_num_idle_workers,
+ mod_spdy::ThreadPool* thread_pool,
+ int timeout_millis) {
+ int millis_remaining = timeout_millis;
+ while (true) {
+ const int actual_num_workers = thread_pool->GetNumWorkersForTest();
+ const int actual_num_idle_workers =
+ thread_pool->GetNumIdleWorkersForTest();
+ if (actual_num_workers == expected_num_workers &&
+ actual_num_idle_workers == expected_num_idle_workers) {
+ return;
+ }
+ if (millis_remaining <= 0) {
+ ADD_FAILURE() << "Timed out; expected " << expected_num_workers
+ << " worker(s) with " << expected_num_idle_workers
+ <<" idle; still at " << actual_num_workers
+ << " worker(s) with " << actual_num_idle_workers
+ << " idle after " << timeout_millis << "ms";
+ return;
+ }
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ millis_remaining -= 10;
+ }
+}
+
+// Test that we spawn new threads as needed, and allow them to die off after
+// being idle for a while.
+TEST(ThreadPoolTest, CreateAndRetireWorkers) {
+ // Create a thread pool with min_threads < max_threads, and give it a short
+ // max_thread_idle_time.
+ const int idle_time_millis = 100;
+ mod_spdy::ThreadPool thread_pool(
+ 2, 4, base::TimeDelta::FromMilliseconds(idle_time_millis));
+ ASSERT_TRUE(thread_pool.Start());
+ // As soon as we start the thread pool, there should be the minimum number of
+ // workers (two), both counted as idle.
+ EXPECT_EQ(2, thread_pool.GetNumWorkersForTest());
+ EXPECT_EQ(2, thread_pool.GetNumIdleWorkersForTest());
+
+ scoped_ptr<mod_spdy::Executor> executor(thread_pool.NewExecutor());
+
+ // Start up three tasks. That should push us up to three workers
+ // immediately. If we make sure to give those threads a chance to run, they
+ // should soon pick up the tasks and all be busy.
+ mod_spdy::testing::Notification done1;
+ executor->AddTask(new WaitFunction(&done1), 0);
+ executor->AddTask(new WaitFunction(&done1), 1);
+ executor->AddTask(new WaitFunction(&done1), 2);
+ EXPECT_EQ(3, thread_pool.GetNumWorkersForTest());
+ ExpectWorkersWithinTimeout(3, 0, &thread_pool, 100);
+
+ // Add three more tasks. We should now be at the maximum number of workers,
+ // and that fourth worker should be busy soon.
+ mod_spdy::testing::Notification done2;
+ executor->AddTask(new WaitFunction(&done2), 1);
+ executor->AddTask(new WaitFunction(&done2), 2);
+ executor->AddTask(new WaitFunction(&done2), 3);
+ EXPECT_EQ(4, thread_pool.GetNumWorkersForTest());
+ ExpectWorkersWithinTimeout(4, 0, &thread_pool, 100);
+
+ // Allow the first group of tasks to finish. There are now only three tasks
+ // running, so one of our four threads should go idle. If we wait for a
+ // while after that, that thread should terminate and enter zombie mode.
+ done1.Set();
+ ExpectWorkersWithinTimeout(4, 1, &thread_pool, idle_time_millis / 2);
+ ExpectWorkersWithinTimeout(3, 0, &thread_pool, 2 * idle_time_millis);
+ EXPECT_EQ(1, thread_pool.GetNumZombiesForTest());
+
+ // Allow the second group of tasks to finish. There are no tasks left, so
+ // all three threads should go idle. If we wait for a while after that,
+ // exactly one of the three should shut down, bringing us back down to the
+ // minimum number of threads. We should now have two zombie threads.
+ done2.Set();
+ ExpectWorkersWithinTimeout(3, 3, &thread_pool, idle_time_millis / 2);
+ ExpectWorkersWithinTimeout(2, 2, &thread_pool, 2 * idle_time_millis);
+ EXPECT_EQ(2, thread_pool.GetNumZombiesForTest());
+
+ // Start some new new tasks. This should cause us to immediately reap the
+ // zombie threads, and soon, we should have three busy threads.
+ mod_spdy::testing::Notification done3;
+ executor->AddTask(new WaitFunction(&done3), 0);
+ executor->AddTask(new WaitFunction(&done3), 2);
+ executor->AddTask(new WaitFunction(&done3), 1);
+ EXPECT_EQ(0, thread_pool.GetNumZombiesForTest());
+ EXPECT_EQ(3, thread_pool.GetNumWorkersForTest());
+ ExpectWorkersWithinTimeout(3, 0, &thread_pool, 100);
+
+ // Let those tasks finish. Once again, the threads should go idle, and then
+ // one of them should terminate and enter zombie mode.
+ done3.Set();
+ ExpectWorkersWithinTimeout(3, 3, &thread_pool, idle_time_millis / 2);
+ ExpectWorkersWithinTimeout(2, 2, &thread_pool, 2 * idle_time_millis);
+ EXPECT_EQ(1, thread_pool.GetNumZombiesForTest());
+
+ // When we exit the test, the thread pool's destructor should reap the zombie
+ // thread (as well as shutting down the still-running workers). We can
+ // verify this by running this test under valgrind and making sure that no
+ // memory is leaked.
+}
+
+} // namespace
--- /dev/null
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// version.h is generated from version.h.in. Edit the source!
+
+#pragma once
+
+// Version Information
+
+#define MOD_SPDY_VERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+#define MOD_SPDY_VERSION_STRING "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+
+// Branding Information
+
+#define COMPANY_FULLNAME_STRING "@COMPANY_FULLNAME@"
+#define COMPANY_SHORTNAME_STRING "@COMPANY_SHORTNAME@"
+#define PRODUCT_FULLNAME_STRING "@PRODUCT_FULLNAME@"
+#define PRODUCT_SHORTNAME_STRING "@PRODUCT_SHORTNAME@"
+#define COPYRIGHT_STRING "@COPYRIGHT@"
+#define OFFICIAL_BUILD_STRING "@OFFICIAL_BUILD@"
+
+// Changelist Information
+
+#define LASTCHANGE_STRING "@LASTCHANGE@"
--- /dev/null
+// Copyright 2010 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.
+
+// References to "TAMB" below refer to _The Apache Modules Book_ by Nick Kew
+// (ISBN: 0-13-240967-4).
+
+#include "mod_spdy/mod_spdy.h"
+
+#include <algorithm> // for std::min
+
+#include "httpd.h"
+#include "http_connection.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "apr_optional.h"
+#include "apr_optional_hooks.h"
+#include "apr_tables.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/apache/apache_spdy_session_io.h"
+#include "mod_spdy/apache/apache_spdy_stream_task_factory.h"
+#include "mod_spdy/apache/config_commands.h"
+#include "mod_spdy/apache/config_util.h"
+#include "mod_spdy/apache/id_pool.h"
+#include "mod_spdy/apache/filters/server_push_filter.h"
+#include "mod_spdy/apache/log_message_handler.h"
+#include "mod_spdy/apache/master_connection_context.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/apache/slave_connection_context.h"
+#include "mod_spdy/apache/slave_connection_api.h"
+#include "mod_spdy/apache/ssl_util.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_session.h"
+#include "mod_spdy/common/thread_pool.h"
+#include "mod_spdy/common/version.h"
+
+extern "C" {
+
+// Declaring mod_so's optional hooks here (so that we don't need to
+// #include "mod_so.h").
+APR_DECLARE_OPTIONAL_FN(module*, ap_find_loaded_module_symbol,
+ (server_rec*, const char*));
+
+// Declaring modified mod_ssl's optional hooks here (so that we don't need to
+// #include "mod_ssl.h").
+APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_advertise_protos_hook,
+ (conn_rec *connection, apr_array_header_t *protos));
+APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_proto_negotiated_hook,
+ (conn_rec *connection, const char *proto_name,
+ apr_size_t proto_name_len));
+
+} // extern "C"
+
+namespace {
+
+const char kFakeModSpdyProtocolName[] =
+ "x-mod-spdy/" MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+COMPILE_ASSERT(arraysize(kFakeModSpdyProtocolName) <= 255,
+ fake_protocol_name_is_not_too_long_for_npn);
+const char kFakeModSpdyProtocolNameNoVersion[] = "x-mod-spdy/no-version";
+COMPILE_ASSERT(arraysize(kFakeModSpdyProtocolNameNoVersion) <= 255,
+ fake_protocol_name_no_version_is_not_too_long_for_npn);
+
+const char* const kHttpProtocolName = "http/1.1";
+const char* const kSpdy2ProtocolName = "spdy/2";
+const char* const kSpdy3ProtocolName = "spdy/3";
+const char* const kSpdy31ProtocolName = "spdy/3.1";
+const char* const kSpdyVersionEnvironmentVariable = "SPDY_VERSION";
+
+const char* const kPhpModuleNames[] = {
+ "php_module",
+ "php2_module",
+ "php3_module",
+ "php4_module",
+ "php5_module",
+ "php6_module"
+};
+
+// This global variable stores the filter handle for our push filter. Normally,
+// global variables would be very dangerous in a concurrent environment like
+// Apache, but this one is okay because it is assigned just once, at
+// start-up (during which Apache is running single-threaded; see TAMB 2.2.1),
+// and are read-only thereafter.
+ap_filter_rec_t* gServerPushFilterHandle = NULL;
+
+// A process-global thread pool for processing SPDY streams concurrently. This
+// is initialized once in *each child process* by our child-init hook. Note
+// that in a non-threaded MPM (e.g. Prefork), this thread pool will be used by
+// just one SPDY connection at a time, but in a threaded MPM (e.g. Worker) it
+// will shared by several SPDY connections at once. That's okay though,
+// because ThreadPool objects are thread-safe. Users just have to make sure
+// that they configure SpdyMaxThreadsPerProcess depending on the MPM.
+mod_spdy::ThreadPool* gPerProcessThreadPool = NULL;
+
+// Optional function provided by mod_spdy. Return zero if the connection is
+// not using SPDY, otherwise return the SPDY version number in use. Note that
+// unlike our private functions, we use Apache C naming conventions for this
+// function because we export it to other modules.
+int spdy_get_version(conn_rec* connection) {
+ if (mod_spdy::HasMasterConnectionContext(connection)) {
+ mod_spdy::MasterConnectionContext* master_context =
+ mod_spdy::GetMasterConnectionContext(connection);
+ if (master_context->is_using_spdy()) {
+ return mod_spdy::SpdyVersionToFramerVersion(
+ master_context->spdy_version());
+ }
+ }
+
+ if (mod_spdy::HasSlaveConnectionContext(connection)) {
+ mod_spdy::SlaveConnectionContext* slave_context =
+ mod_spdy::GetSlaveConnectionContext(connection);
+ if (slave_context->spdy_version() != mod_spdy::spdy::SPDY_VERSION_NONE) {
+ return mod_spdy::SpdyVersionToFramerVersion(
+ slave_context->spdy_version());
+ }
+ }
+
+ return 0;
+}
+
+apr_status_t ServerPushFilterFunc(ap_filter_t* filter,
+ apr_bucket_brigade* input_brigade) {
+ mod_spdy::ServerPushFilter* server_push_filter =
+ static_cast<mod_spdy::ServerPushFilter*>(filter->ctx);
+ return server_push_filter->Write(filter, input_brigade);
+}
+
+// Called on server startup, after all modules have loaded.
+void RetrieveOptionalFunctions() {
+ mod_spdy::RetrieveModSslFunctions();
+}
+
+// Called after configuration has completed.
+int PostConfig(apr_pool_t* pconf, apr_pool_t* plog, apr_pool_t* ptemp,
+ server_rec* server_list) {
+ mod_spdy::ScopedServerLogHandler log_handler(server_list);
+
+ // Check if any of the virtual hosts have mod_spdy enabled.
+ bool any_enabled = false;
+ for (server_rec* server = server_list; server != NULL;
+ server = server->next) {
+ if (mod_spdy::GetServerConfig(server)->spdy_enabled()) {
+ any_enabled = true;
+ break;
+ }
+ }
+
+ // Log a message indicating whether mod_spdy is enabled or not. It's all too
+ // easy to install mod_spdy and forget to turn it on, so this may be helpful
+ // for debugging server behavior.
+ if (!any_enabled) {
+ LOG(WARNING) << "mod_spdy is installed, but has not been enabled in the "
+ << "Apache config. SPDY will not be used by this server. "
+ << "See http://code.google.com/p/mod-spdy/wiki/ConfigOptions "
+ << "for how to enable.";
+ }
+
+
+ // Modules which may not be thread-safe shouldn't be used with mod_spdy.
+ // That mainly seems to be mod_php. If mod_php is installed, log a warning
+ // pointing the user to docs on how to use PHP safely with mod_spdy.
+ if (any_enabled) {
+ module* (*get_module)(server_rec*, const char*) =
+ APR_RETRIEVE_OPTIONAL_FN(ap_find_loaded_module_symbol);
+ if (get_module != NULL) {
+ for (size_t i = 0; i < arraysize(kPhpModuleNames); ++i) {
+ if (get_module(server_list, kPhpModuleNames[i]) != NULL) {
+ LOG(WARNING)
+ << kPhpModuleNames[i] << " may not be thread-safe, and "
+ << "should not be used with mod_spdy. Instead, see "
+ << "https://developers.google.com/speed/spdy/mod_spdy/php for "
+ << "how to configure your server to use PHP safely.";
+ }
+ }
+ }
+ }
+
+ return OK;
+}
+
+// Called exactly once for each child process, before that process starts
+// spawning worker threads.
+void ChildInit(apr_pool_t* pool, server_rec* server_list) {
+ mod_spdy::ScopedServerLogHandler log_handler(server_list);
+
+ // Check whether mod_spdy is enabled for any server_rec in the list, and
+ // determine the most verbose log level of any server in the list.
+ bool spdy_enabled = false;
+ int max_apache_log_level = APLOG_EMERG; // the least verbose log level
+ COMPILE_ASSERT(APLOG_INFO > APLOG_ERR, bigger_number_means_more_verbose);
+ for (server_rec* server = server_list; server != NULL;
+ server = server->next) {
+ spdy_enabled |= mod_spdy::GetServerConfig(server)->spdy_enabled();
+ if (server->loglevel > max_apache_log_level) {
+ max_apache_log_level = server->loglevel;
+ }
+ }
+
+ // There are a couple config options we need to check (vlog_level and
+ // max_threads_per_process) that are only settable at the top level of the
+ // config, so it doesn't matter which server in the list we read them from.
+ const mod_spdy::SpdyServerConfig* top_level_config =
+ mod_spdy::GetServerConfig(server_list);
+
+ // We set mod_spdy's global logging level to that of the most verbose server
+ // in the list. The scoped logging handlers we establish will sometimes
+ // restrict things further, if they are for a less verbose virtual host.
+ mod_spdy::SetLoggingLevel(max_apache_log_level,
+ top_level_config->vlog_level());
+
+ // If mod_spdy is not enabled on any server_rec, don't do any other setup.
+ if (!spdy_enabled) {
+ return;
+ }
+
+ // Create the per-process thread pool.
+ const int max_threads = top_level_config->max_threads_per_process();
+ const int min_threads =
+ std::min(max_threads, top_level_config->min_threads_per_process());
+ scoped_ptr<mod_spdy::ThreadPool> thread_pool(
+ new mod_spdy::ThreadPool(min_threads, max_threads));
+ if (thread_pool->Start()) {
+ gPerProcessThreadPool = thread_pool.release();
+ mod_spdy::PoolRegisterDelete(pool, gPerProcessThreadPool);
+ } else {
+ LOG(DFATAL) << "Could not create mod_spdy thread pool; "
+ << "mod_spdy will not function.";
+ }
+}
+
+// A pre-connection hook, to be run _before_ mod_ssl's pre-connection hook.
+// Disables mod_ssl for our slave connections.
+int DisableSslForSlaves(conn_rec* connection, void* csd) {
+ mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+ if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+ // For master connections, the context object should't have been created
+ // yet (it gets created in PreConnection).
+ DCHECK(!mod_spdy::HasMasterConnectionContext(connection));
+ return DECLINED; // only do things for slave connections.
+ }
+
+ // If a slave context has already been created, mod_spdy must be enabled.
+ DCHECK(mod_spdy::GetServerConfig(connection)->spdy_enabled());
+
+ // Disable mod_ssl for the slave connection so it doesn't get in our way.
+ if (!mod_spdy::DisableSslForConnection(connection)) {
+ // Hmm, mod_ssl either isn't installed or isn't enabled. That should be
+ // impossible (we wouldn't _have_ a slave connection without having SSL for
+ // the master connection), unless we're configured to assume SPDY for
+ // non-SSL connections. Let's check if that's the case, and LOG(DFATAL) if
+ // it's not.
+ if (mod_spdy::GetServerConfig(connection)->
+ use_spdy_version_without_ssl() == mod_spdy::spdy::SPDY_VERSION_NONE) {
+ LOG(DFATAL) << "mod_ssl missing for slave connection";
+ }
+ }
+ return OK;
+}
+
+// A pre-connection hook, to be run _after_ mod_ssl's pre-connection hook, but
+// just _before_ the core pre-connection hook. For master connections, this
+// checks if SSL is active; for slave connections, this adds our
+// connection-level filters and prevents core filters from being inserted.
+int PreConnection(conn_rec* connection, void* csd) {
+ mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+ // If a slave context has not yet been created, this is a "real" connection.
+ if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+ // Master context should not have been created yet, either.
+ DCHECK(!mod_spdy::HasMasterConnectionContext(connection));
+
+ // If mod_spdy is disabled on this server, don't allocate our context
+ // object.
+ const mod_spdy::SpdyServerConfig* config =
+ mod_spdy::GetServerConfig(connection);
+ if (!config->spdy_enabled()) {
+ return DECLINED;
+ }
+
+ // We'll set this to a nonzero SPDY version number momentarily if we're
+ // configured to assume a particular SPDY version for this connection.
+ mod_spdy::spdy::SpdyVersion assume_spdy_version =
+ mod_spdy::spdy::SPDY_VERSION_NONE;
+
+ // Check if this connection is over SSL; if not, we can't do NPN, so we
+ // definitely won't be using SPDY (unless we're configured to assume SPDY
+ // for non-SSL connections).
+ const bool using_ssl = mod_spdy::IsUsingSslForConnection(connection);
+ if (!using_ssl) {
+ // This is not an SSL connection, so we can't talk SPDY on it _unless_ we
+ // have opted to assume SPDY over non-SSL connections (presumably for
+ // debugging purposes; this would normally break browsers).
+ assume_spdy_version = config->use_spdy_version_without_ssl();
+ if (assume_spdy_version == mod_spdy::spdy::SPDY_VERSION_NONE) {
+ return DECLINED;
+ }
+ }
+
+ // Okay, we've got a real connection over SSL, so we'll be negotiating with
+ // the client to see if we can use SPDY for this connection. Create our
+ // connection context object to keep track of the negotiation.
+ mod_spdy::MasterConnectionContext* master_context =
+ mod_spdy::CreateMasterConnectionContext(connection, using_ssl);
+ // If we're assuming SPDY for this connection, it means we know NPN won't
+ // happen at all, and we're just going to assume a particular SPDY version.
+ if (assume_spdy_version != mod_spdy::spdy::SPDY_VERSION_NONE) {
+ master_context->set_assume_spdy(true);
+ master_context->set_spdy_version(assume_spdy_version);
+ }
+ return OK;
+ }
+ // If the context has already been created, this is a slave connection.
+ else {
+ mod_spdy::SlaveConnectionContext* slave_context =
+ mod_spdy::GetSlaveConnectionContext(connection);
+
+ DCHECK(mod_spdy::GetServerConfig(connection)->spdy_enabled());
+
+ // Add our input and output filters.
+ ap_add_input_filter_handle(
+ slave_context->input_filter_handle(), // filter handle
+ slave_context->input_filter_context(), // context (any void* we want)
+ NULL, // request object
+ connection); // connection object
+
+ ap_add_output_filter_handle(
+ slave_context->output_filter_handle(), // filter handle
+ slave_context->output_filter_context(), // context (any void* we want)
+ NULL, // request object
+ connection); // connection object
+
+ // Prevent core pre-connection hooks from running (thus preventing core
+ // filters from being inserted).
+ return DONE;
+ }
+}
+
+// Called to see if we want to take care of processing this connection -- if
+// so, we do so and return OK, otherwise we return DECLINED. For slave
+// connections, we want to return DECLINED. For "real" connections, we need to
+// determine if they are using SPDY; if not we returned DECLINED, but if so we
+// process this as a master SPDY connection and then return OK.
+int ProcessConnection(conn_rec* connection) {
+ mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+ // If mod_spdy is disabled on this server, don't use SPDY.
+ const mod_spdy::SpdyServerConfig* config =
+ mod_spdy::GetServerConfig(connection);
+ if (!config->spdy_enabled()) {
+ return DECLINED;
+ }
+
+ // We do not want to attach to non-inbound connections (e.g. connections
+ // created by mod_proxy). Non-inbound connections do not get a scoreboard
+ // hook, so we abort if the connection doesn't have the scoreboard hook. See
+ // http://mail-archives.apache.org/mod_mbox/httpd-dev/201008.mbox/%3C99EA83DCDE961346AFA9B5EC33FEC08B047FDC26@VF-MBX11.internal.vodafone.com%3E
+ // for more details.
+ if (connection->sbh == NULL) {
+ return DECLINED;
+ }
+
+ // Our connection context object will have been created by now, unless our
+ // pre-connection hook saw that this was a non-SSL connection, in which case
+ // we won't be using SPDY so we can stop now. It may also mean that this is
+ // a slave connection, in which case we don't want to deal with it here --
+ // instead we will let Apache treat it like a regular HTTP connection.
+ if (!mod_spdy::HasMasterConnectionContext(connection)) {
+ return DECLINED;
+ }
+
+ mod_spdy::MasterConnectionContext* master_context =
+ mod_spdy::GetMasterConnectionContext(connection);
+
+ // In the unlikely event that we failed to create our per-process thread
+ // pool, we're not going to be able to operate.
+ if (gPerProcessThreadPool == NULL) {
+ return DECLINED;
+ }
+
+ // Unless we're simply assuming SPDY for this connection, we need to do NPN
+ // to decide whether to use SPDY or not.
+ if (!master_context->is_assuming_spdy()) {
+ // We need to pull some data through mod_ssl in order to force the SSL
+ // handshake, and hence NPN, to take place. To that end, perform a small
+ // SPECULATIVE read (and then throw away whatever data we got).
+ apr_bucket_brigade* temp_brigade =
+ apr_brigade_create(connection->pool, connection->bucket_alloc);
+ const apr_status_t status =
+ ap_get_brigade(connection->input_filters, temp_brigade,
+ AP_MODE_SPECULATIVE, APR_BLOCK_READ, 1);
+ apr_brigade_destroy(temp_brigade);
+
+ // If we were unable to pull any data through, give up and return DECLINED.
+ if (status != APR_SUCCESS) {
+ // Depending on exactly what went wrong, we may want to log something
+ // before returning DECLINED.
+ if (APR_STATUS_IS_EOF(status)) {
+ // EOF errors are to be expected sometimes (e.g. if the connection was
+ // closed), and we should just quietly give up. No need to log in this
+ // case.
+ } else if (APR_STATUS_IS_TIMEUP(status)) {
+ // TIMEUP errors also seem to happen occasionally. I think we should
+ // also give up in this case, but I'm not sure yet; for now let's VLOG
+ // when it happens, to help with debugging [mdsteele].
+ VLOG(1) << "Couldn't read from SSL connection (TIMEUP).";
+ } else {
+ // Any other error could be a real issue, so let's log it (slightly)
+ // more noisily.
+ LOG(INFO) << "Couldn't read from SSL connection; failed with status "
+ << status << ": " << mod_spdy::AprStatusString(status);
+ }
+ return DECLINED;
+ }
+
+ // If we did pull some data through, then NPN should have happened and our
+ // OnNextProtocolNegotiated() hook should have been called by now. If NPN
+ // hasn't happened, it's probably because we're using an old version of
+ // mod_ssl that doesn't support NPN, in which case we should probably warn
+ // the user that mod_spdy isn't going to work.
+ if (master_context->npn_state() ==
+ mod_spdy::MasterConnectionContext::NOT_DONE_YET) {
+ LOG(WARNING)
+ << "NPN didn't happen during SSL handshake. You're probably using "
+ << "a version of mod_ssl that doesn't support NPN. Without NPN "
+ << "support, the server cannot use SPDY. See "
+ << "http://code.google.com/p/mod-spdy/wiki/GettingStarted for more "
+ << "information on installing a version of mod_spdy with NPN "
+ << "support.";
+ }
+ }
+
+ // If NPN didn't choose SPDY, then don't use SPDY.
+ if (!master_context->is_using_spdy()) {
+ return DECLINED;
+ }
+
+ const mod_spdy::spdy::SpdyVersion spdy_version =
+ master_context->spdy_version();
+ LOG(INFO) << "Starting SPDY/" <<
+ mod_spdy::SpdyVersionNumberString(spdy_version) << " session";
+
+ // At this point, we and the client have agreed to use SPDY (either that, or
+ // we've been configured to use SPDY regardless of what the client says), so
+ // process this as a SPDY master connection.
+ mod_spdy::ApacheSpdySessionIO session_io(connection);
+ mod_spdy::ApacheSpdyStreamTaskFactory task_factory(connection);
+ scoped_ptr<mod_spdy::Executor> executor(
+ gPerProcessThreadPool->NewExecutor());
+ mod_spdy::SpdySession spdy_session(
+ spdy_version, config, &session_io, &task_factory, executor.get());
+ // This call will block until the session has closed down.
+ spdy_session.Run();
+
+ LOG(INFO) << "Terminating SPDY/" <<
+ mod_spdy::SpdyVersionNumberString(spdy_version) << " session";
+
+ // Return OK to tell Apache that we handled this connection.
+ return OK;
+}
+
+// Called by mod_ssl when it needs to decide what protocols to advertise to the
+// client during Next Protocol Negotiation (NPN).
+int AdvertiseSpdy(conn_rec* connection, apr_array_header_t* protos) {
+ // If mod_spdy is disabled on this server, then we shouldn't advertise SPDY
+ // to the client.
+ if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+ return DECLINED;
+ }
+
+ // Advertise SPDY to the client. We push protocol names in descending order
+ // of preference; the one we'd most prefer comes first.
+ APR_ARRAY_PUSH(protos, const char*) = kSpdy31ProtocolName;
+ APR_ARRAY_PUSH(protos, const char*) = kSpdy3ProtocolName;
+ APR_ARRAY_PUSH(protos, const char*) = kSpdy2ProtocolName;
+ return OK;
+}
+
+// Called by mod_ssl (along with the AdvertiseSpdy function) when it needs to
+// decide what protocols to advertise to the client during Next Protocol
+// Negotiation (NPN). These two functions are separate so that AdvertiseSpdy
+// can run early in the hook order, and AdvertiseHttp can run late.
+int AdvertiseHttp(conn_rec* connection, apr_array_header_t* protos) {
+ const mod_spdy::SpdyServerConfig* config =
+ mod_spdy::GetServerConfig(connection);
+ // If mod_spdy is disabled on this server, don't do anything.
+ if (!config->spdy_enabled()) {
+ return DECLINED;
+ }
+
+ // Apache definitely supports HTTP/1.1, and so it ought to advertise it
+ // during NPN. However, the Apache core HTTP module doesn't yet know about
+ // this hook, so we advertise HTTP/1.1 for them. But to be future-proof, we
+ // don't add "http/1.1" to the list if it's already there.
+ bool http_not_advertised = true;
+ for (int i = 0; i < protos->nelts; ++i) {
+ if (!strcmp(APR_ARRAY_IDX(protos, i, const char*), kHttpProtocolName)) {
+ http_not_advertised = false;
+ break;
+ }
+ }
+ if (http_not_advertised) {
+ // No one's advertised HTTP/1.1 yet, so let's do it now.
+ APR_ARRAY_PUSH(protos, const char*) = kHttpProtocolName;
+ }
+
+ // Advertise a fake protocol, indicating the mod_spdy version in use. We
+ // push this last, so the client doesn't think we prefer it to HTTP.
+ if (config->send_version_header()) {
+ APR_ARRAY_PUSH(protos, const char*) = kFakeModSpdyProtocolName;
+ } else {
+ // If the user prefers not to send a version number, leave out the version
+ // number.
+ APR_ARRAY_PUSH(protos, const char*) = kFakeModSpdyProtocolNameNoVersion;
+ }
+
+ return OK;
+}
+
+// Called by mod_ssl after Next Protocol Negotiation (NPN) has completed,
+// informing us which protocol was chosen by the client.
+int OnNextProtocolNegotiated(conn_rec* connection, const char* proto_name,
+ apr_size_t proto_name_len) {
+ mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+ // If mod_spdy is disabled on this server, then ignore the results of NPN.
+ if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+ return DECLINED;
+ }
+
+ // We disable mod_ssl for slave connections, so NPN shouldn't be happening
+ // unless this is a non-slave connection.
+ if (mod_spdy::HasSlaveConnectionContext(connection)) {
+ LOG(DFATAL) << "mod_ssl was aparently not disabled for slave connection";
+ return DECLINED;
+ }
+
+ // Given that mod_spdy is enabled, our context object should have already
+ // been created in our pre-connection hook, unless this is a non-SSL
+ // connection. But if it's a non-SSL connection, then NPN shouldn't be
+ // happening, and this hook shouldn't be getting called! So, let's
+ // LOG(DFATAL) if context is NULL here.
+ if (!mod_spdy::HasMasterConnectionContext(connection)) {
+ LOG(DFATAL) << "NPN happened, but there is no connection context.";
+ return DECLINED;
+ }
+
+ mod_spdy::MasterConnectionContext* master_context =
+ mod_spdy::GetMasterConnectionContext(connection);
+
+ // NPN should happen only once, so npn_state should still be NOT_DONE_YET.
+ if (master_context->npn_state() !=
+ mod_spdy::MasterConnectionContext::NOT_DONE_YET) {
+ LOG(DFATAL) << "NPN happened twice.";
+ return DECLINED;
+ }
+
+ // If the client chose the SPDY version that we advertised, then mark this
+ // connection as using SPDY.
+ const base::StringPiece protocol_name(proto_name, proto_name_len);
+ if (protocol_name == kSpdy2ProtocolName) {
+ master_context->set_npn_state(
+ mod_spdy::MasterConnectionContext::USING_SPDY);
+ master_context->set_spdy_version(mod_spdy::spdy::SPDY_VERSION_2);
+ } else if (protocol_name == kSpdy3ProtocolName) {
+ master_context->set_npn_state(
+ mod_spdy::MasterConnectionContext::USING_SPDY);
+ master_context->set_spdy_version(mod_spdy::spdy::SPDY_VERSION_3);
+ } else if (protocol_name == kSpdy31ProtocolName) {
+ master_context->set_npn_state(
+ mod_spdy::MasterConnectionContext::USING_SPDY);
+ master_context->set_spdy_version(mod_spdy::spdy::SPDY_VERSION_3_1);
+ }
+ // Otherwise, explicitly mark this connection as not using SPDY.
+ else {
+ master_context->set_npn_state(
+ mod_spdy::MasterConnectionContext::NOT_USING_SPDY);
+ }
+ return OK;
+}
+
+int SetUpSubprocessEnv(request_rec* request) {
+ conn_rec* connection = request->connection;
+ mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+ // If mod_spdy is disabled on this server, then don't do anything.
+ if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+ return DECLINED;
+ }
+
+ // Don't do anything unless this is a slave connection.
+ if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+ return DECLINED;
+ }
+
+ mod_spdy::SlaveConnectionContext* slave_context =
+ mod_spdy::GetSlaveConnectionContext(connection);
+
+ // If this request is over SPDY (which it might not be, if this slave
+ // connection is being used by another module through the slave connection
+ // API), then for the benefit of CGI scripts, which have no way of calling
+ // spdy_get_version(), set an environment variable indicating what SPDY
+ // version is being used, allowing them to optimize the response for SPDY.
+ // See http://code.google.com/p/mod-spdy/issues/detail?id=27 for details.
+ const mod_spdy::spdy::SpdyVersion spdy_version =
+ slave_context->spdy_version();
+ if (spdy_version != mod_spdy::spdy::SPDY_VERSION_NONE) {
+ apr_table_set(request->subprocess_env, kSpdyVersionEnvironmentVariable,
+ mod_spdy::SpdyVersionNumberString(spdy_version));
+ }
+
+ // Normally, mod_ssl sets the HTTPS environment variable to "on" for requests
+ // served over SSL. We turn mod_ssl off for our slave connections, but those
+ // requests _are_ (usually) being served over SSL (via the master
+ // connection), so we set the variable ourselves if we are in fact using SSL.
+ // See http://code.google.com/p/mod-spdy/issues/detail?id=32 for details.
+ if (slave_context->is_using_ssl()) {
+ apr_table_setn(request->subprocess_env, "HTTPS", "on");
+ }
+
+ return OK;
+}
+
+void InsertRequestFilters(request_rec* request) {
+ conn_rec* const connection = request->connection;
+ mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+ // If mod_spdy is disabled on this server, then don't do anything.
+ if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+ return;
+ }
+
+ // Don't do anything unless this is a slave connection.
+ if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+ return;
+ }
+
+ mod_spdy::SlaveConnectionContext* slave_context =
+ mod_spdy::GetSlaveConnectionContext(connection);
+
+ // Insert a filter that will initiate server pushes when so instructed (such
+ // as by an X-Associated-Content header). This is conditional on this
+ // connection being managed entirely on mod_spdy, and not being done on
+ // behalf of someone else using the slave connection API.
+ if (slave_context->slave_stream() != NULL) {
+ mod_spdy::ServerPushFilter* server_push_filter =
+ new mod_spdy::ServerPushFilter(slave_context->slave_stream(), request,
+ mod_spdy::GetServerConfig(request));
+ PoolRegisterDelete(request->pool, server_push_filter);
+ ap_add_output_filter_handle(
+ gServerPushFilterHandle, // filter handle
+ server_push_filter, // context (any void* we want)
+ request, // request object
+ connection); // connection object
+ }
+}
+
+apr_status_t InvokeIdPoolDestroyInstance(void*) {
+ mod_spdy::IdPool::DestroyInstance();
+ return APR_SUCCESS;
+}
+
+// Called when the module is loaded to register all of our hook functions.
+void RegisterHooks(apr_pool_t* pool) {
+ mod_spdy::InstallLogMessageHandler(pool);
+ mod_spdy::IdPool::CreateInstance();
+ apr_pool_cleanup_register(pool, NULL, InvokeIdPoolDestroyInstance,
+ apr_pool_cleanup_null /* no cleanup on fork*/);
+
+ static const char* const modules_core[] = {"core.c", NULL};
+ static const char* const modules_mod_ssl[] = {"mod_ssl.c", NULL};
+
+ // Register a hook to be called after all modules have been loaded, so we can
+ // retrieve optional functions from mod_ssl.
+ ap_hook_optional_fn_retrieve(
+ RetrieveOptionalFunctions, // hook function to be called
+ NULL, // predecessors
+ NULL, // successors
+ APR_HOOK_MIDDLE); // position
+
+ // Register a hook to be called after configuration has completed. We use
+ // this hook to log whether or not mod_spdy is enabled on this server.
+ ap_hook_post_config(PostConfig, NULL, NULL, APR_HOOK_MIDDLE);
+
+ // Register a hook to be called once for each child process spawned by
+ // Apache, before the MPM starts spawning worker threads. We use this hook
+ // to initialize our per-process thread pool.
+ ap_hook_child_init(ChildInit, NULL, NULL, APR_HOOK_MIDDLE);
+
+ // Register a pre-connection hook to turn off mod_ssl for our slave
+ // connections. This must run before mod_ssl's pre-connection hook, so that
+ // we can disable mod_ssl before it inserts its filters, so we name mod_ssl
+ // as an explicit successor.
+ ap_hook_pre_connection(
+ DisableSslForSlaves, // hook function to be called
+ NULL, // predecessors
+ modules_mod_ssl, // successors
+ APR_HOOK_FIRST); // position
+
+ // Register our pre-connection hook, which will be called shortly before our
+ // process-connection hook. The hooking order is very important here. In
+ // particular:
+ // * We must run before the core pre-connection hook, so that we can return
+ // DONE and stop the core filters from being inserted. Thus, we name
+ // core.c as a successor.
+ // * We should run after almost all other modules (except core.c) so that
+ // our returning DONE doesn't prevent other modules from working. Thus,
+ // we use APR_HOOK_LAST for our position argument.
+ // * In particular, we MUST run after mod_ssl's pre-connection hook, so
+ // that we can ask mod_ssl if this connection is using SSL. Thus, we
+ // name mod_ssl.c as a predecessor. This is redundant, since mod_ssl's
+ // pre-connection hook uses APR_HOOK_MIDDLE, but it's good to be sure.
+ // For more about controlling hook order, see TAMB 10.2.2 or
+ // http://httpd.apache.org/docs/trunk/developer/hooks.html#hooking-order
+ ap_hook_pre_connection(
+ PreConnection, // hook function to be called
+ modules_mod_ssl, // predecessors
+ modules_core, // successors
+ APR_HOOK_LAST); // position
+
+ // Register our process-connection hook, which will handle SPDY connections.
+ // The first process-connection hook in the chain to return OK gets to be in
+ // charge of handling the connection from start to finish, so we put
+ // ourselves in APR_HOOK_FIRST so we can get an early look at the connection.
+ // If it turns out not to be a SPDY connection, we'll get out of the way and
+ // let other modules deal with it.
+ ap_hook_process_connection(ProcessConnection, NULL, NULL, APR_HOOK_FIRST);
+
+ // For the benefit of e.g. PHP/CGI scripts, we need to set various subprocess
+ // environment variables for each request served via SPDY. Register a hook
+ // to do so; we use the fixup hook for this because that's the same hook that
+ // mod_ssl uses for setting its subprocess environment variables.
+ ap_hook_fixups(SetUpSubprocessEnv, NULL, NULL, APR_HOOK_MIDDLE);
+
+ // Our server push filter is a request-level filter, so we insert it with the
+ // insert-filter hook.
+ ap_hook_insert_filter(InsertRequestFilters, NULL, NULL, APR_HOOK_MIDDLE);
+
+ // Register a hook with mod_ssl to be called when deciding what protocols to
+ // advertise during Next Protocol Negotiatiation (NPN); we'll use this
+ // opportunity to advertise that we support SPDY. This hook is declared in
+ // mod_ssl.h, for appropriately-patched versions of mod_ssl. See TAMB 10.2.3
+ // for more about optional hooks.
+ APR_OPTIONAL_HOOK(
+ modssl, // prefix of optional hook
+ npn_advertise_protos_hook, // name of optional hook
+ AdvertiseSpdy, // hook function to be called
+ NULL, // predecessors
+ NULL, // successors
+ APR_HOOK_MIDDLE); // position
+ // If we're advertising SPDY support via NPN, we ought to also advertise HTTP
+ // support. Ideally, the Apache core HTTP module would do this, but for now
+ // it doesn't, so we'll do it for them. We use APR_HOOK_LAST here, since
+ // http/1.1 is our last choice. Note that our AdvertiseHttp function won't
+ // add "http/1.1" to the list if it's already there, so this is future-proof.
+ APR_OPTIONAL_HOOK(modssl, npn_advertise_protos_hook,
+ AdvertiseHttp, NULL, NULL, APR_HOOK_LAST);
+
+ // Register a hook with mod_ssl to be called when NPN has been completed and
+ // the next protocol decided upon. This hook will check if we're actually to
+ // be using SPDY with the client, and enable this module if so. This hook is
+ // declared in mod_ssl.h, for appropriately-patched versions of mod_ssl.
+ APR_OPTIONAL_HOOK(
+ modssl, // prefix of optional hook
+ npn_proto_negotiated_hook, // name of optional hook
+ OnNextProtocolNegotiated, // hook function to be called
+ NULL, // predecessors
+ NULL, // successors
+ APR_HOOK_MIDDLE); // position
+
+ // Create the various filters that will be used to route bytes to/from us
+ // on slave connections.
+ mod_spdy::ApacheSpdyStreamTaskFactory::InitFilters();
+
+ // Also create the filter we will use to detect us being instructed to
+ // do server pushes.
+ gServerPushFilterHandle = ap_register_output_filter(
+ "SPDY_SERVER_PUSH", // name
+ ServerPushFilterFunc, // filter function
+ NULL, // init function (n/a in our case)
+ // We use PROTOCOL-1 so that we come in just before the core HTTP_HEADER
+ // filter serializes the response header table. That way we have a
+ // chance to remove the X-Associated-Content header before it is sent to
+ // the client, while still letting us run as late as possible so that we
+ // can catch headers set by a variety of modules (for example,
+ // mod_headers doesn't run until the CONTENT_SET stage, so if we ran at
+ // the RESOURCE stage, that would be too early).
+ static_cast<ap_filter_type>(AP_FTYPE_PROTOCOL - 1));
+
+ // Register our optional functions, so that other modules can retrieve and
+ // use them. See TAMB 10.1.2.
+ APR_REGISTER_OPTIONAL_FN(spdy_get_version);
+ ModSpdyExportSlaveConnectionFunctions();
+}
+
+} // namespace
+
+extern "C" {
+
+ // Export our module so Apache is able to load us.
+ // See http://gcc.gnu.org/wiki/Visibility for more information.
+#if defined(__linux)
+#pragma GCC visibility push(default)
+#endif
+
+ // Declare our module object (note that "module" is a typedef for "struct
+ // module_struct"; see http_config.h for the definition of module_struct).
+ module AP_MODULE_DECLARE_DATA spdy_module = {
+ // This next macro indicates that this is a (non-MPM) Apache 2.0 module
+ // (the macro actually expands to multiple comma-separated arguments; see
+ // http_config.h for the definition):
+ STANDARD20_MODULE_STUFF,
+
+ // These next four arguments are callbacks for manipulating configuration
+ // structures (the ones we don't need are left null):
+ NULL, // create per-directory config structure
+ NULL, // merge per-directory config structures
+ mod_spdy::CreateSpdyServerConfig, // create per-server config structure
+ mod_spdy::MergeSpdyServerConfigs, // merge per-server config structures
+
+ // This argument supplies a table describing the configuration directives
+ // implemented by this module:
+ mod_spdy::kSpdyConfigCommands,
+
+ // Finally, this function will be called to register hooks for this module:
+ RegisterHooks
+ };
+
+#if defined(__linux)
+#pragma GCC visibility pop
+#endif
+
+} // extern "C"
--- /dev/null
+# Copyright 2010 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.
+
+{
+ 'variables': {
+ # Turning on chromium_code mode enables extra compiler warnings. See
+ # src/build/common.gypi.
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'spdy_common',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+ '<(DEPTH)/net/net.gyp:instaweb_util',
+ '<(DEPTH)/net/net.gyp:spdy',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/net/net.gyp:spdy',
+ ],
+ 'sources': [
+ 'common/executor.cc',
+ 'common/http_request_visitor_interface.cc',
+ 'common/http_response_parser.cc',
+ 'common/http_response_visitor_interface.cc',
+ 'common/http_string_builder.cc',
+ 'common/http_to_spdy_converter.cc',
+ 'common/protocol_util.cc',
+ 'common/server_push_discovery_learner.cc',
+ 'common/server_push_discovery_session.cc',
+ 'common/shared_flow_control_window.cc',
+ 'common/spdy_frame_priority_queue.cc',
+ 'common/spdy_frame_queue.cc',
+ 'common/spdy_server_config.cc',
+ 'common/spdy_server_push_interface.cc',
+ 'common/spdy_session.cc',
+ 'common/spdy_session_io.cc',
+ 'common/spdy_stream.cc',
+ 'common/spdy_stream_task_factory.cc',
+ 'common/spdy_to_http_converter.cc',
+ 'common/thread_pool.cc',
+ ],
+ },
+ {
+ 'target_name': 'spdy_apache',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'spdy_common',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'export_dependent_settings': [
+ 'spdy_common',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ ],
+ 'sources': [
+ 'apache/apache_spdy_session_io.cc',
+ 'apache/apache_spdy_stream_task_factory.cc',
+ 'apache/config_commands.cc',
+ 'apache/config_util.cc',
+ 'apache/filters/http_to_spdy_filter.cc',
+ 'apache/filters/server_push_filter.cc',
+ 'apache/filters/spdy_to_http_filter.cc',
+ 'apache/id_pool.cc',
+ 'apache/log_message_handler.cc',
+ 'apache/master_connection_context.cc',
+ 'apache/pool_util.cc',
+ 'apache/sockaddr_util.cc',
+ 'apache/slave_connection.cc',
+ 'apache/slave_connection_api.cc',
+ 'apache/slave_connection_context.cc',
+ 'apache/ssl_util.cc',
+ ],
+ },
+ {
+ 'target_name': 'mod_spdy',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'spdy_apache',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+ '<(DEPTH)/net/net.gyp:spdy',
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'mod_spdy.cc',
+ ],
+ 'conditions': [['OS == "mac"', {
+ 'xcode_settings': {
+ # We must null out these two variables when building this target,
+ # because it is a loadable_module (-bundle).
+ 'DYLIB_COMPATIBILITY_VERSION':'',
+ 'DYLIB_CURRENT_VERSION':'',
+ }
+ }]],
+ },
+ {
+ 'target_name': 'spdy_common_testing',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/net/net.gyp:instaweb_util',
+ '<(DEPTH)/net/net.gyp:spdy',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/net/net.gyp:spdy',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ ],
+ 'sources': [
+ 'common/testing/async_task_runner.cc',
+ 'common/testing/notification.cc',
+ 'common/testing/spdy_frame_matchers.cc',
+ ],
+ },
+ {
+ 'target_name': 'spdy_common_test',
+ 'type': 'executable',
+ 'dependencies': [
+ 'spdy_common',
+ 'spdy_common_testing',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ '<(DEPTH)/testing/gtest.gyp:gtest_main',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'common/http_response_parser_test.cc',
+ 'common/http_to_spdy_converter_test.cc',
+ 'common/protocol_util_test.cc',
+ 'common/server_push_discovery_learner_test.cc',
+ 'common/server_push_discovery_session_test.cc',
+ 'common/shared_flow_control_window_test.cc',
+ 'common/spdy_frame_priority_queue_test.cc',
+ 'common/spdy_frame_queue_test.cc',
+ 'common/spdy_session_test.cc',
+ 'common/spdy_stream_test.cc',
+ 'common/spdy_to_http_converter_test.cc',
+ 'common/thread_pool_test.cc',
+ ],
+ },
+ {
+ 'target_name': 'spdy_apache_test',
+ 'type': 'executable',
+ 'dependencies': [
+ 'spdy_apache',
+ 'spdy_common_testing',
+ '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:apr',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:aprutil',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'apache/filters/http_to_spdy_filter_test.cc',
+ 'apache/filters/server_push_filter_test.cc',
+ 'apache/filters/spdy_to_http_filter_test.cc',
+ 'apache/id_pool_test.cc',
+ 'apache/pool_util_test.cc',
+ 'apache/sockaddr_util_test.cc',
+ 'apache/testing/dummy_util_filter.cc',
+ 'apache/testing/spdy_apache_test_main.cc',
+ ],
+ },
+ ],
+}
--- /dev/null
+/* Copyright 2011 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.
+ */
+
+/* This is a public header file, to be used by other Apache modules. So,
+ * identifiers declared here should follow Apache module naming conventions
+ * (specifically, identifiers should be lowercase_with_underscores, and our
+ * identifiers should start with the spdy_ prefix), and this header file must
+ * be valid in old-school C (not just C++). */
+
+#ifndef MOD_SPDY_MOD_SPDY_H_
+#define MOD_SPDY_MOD_SPDY_H_
+
+#include "httpd.h"
+#include "apr_optional.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** An optional function which returns zero if the given connection is _not_
+ * using SPDY, and otherwise returns the (non-zero) SPDY protocol version
+ * number being used on the connection. This can be used e.g. to alter the
+ * response for a given request to optimize for SPDY if SPDY is being used. */
+APR_DECLARE_OPTIONAL_FN(int, spdy_get_version, (conn_rec*));
+
+/* TODO(mdsteele): Add an optional function for doing a SPDY server push. */
+
+/* TODO(mdsteele): Consider adding an optional function to tell mod_spdy NOT to
+ * use SPDY for a connection (similar to ssl_engine_disable in mod_ssl). */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MOD_SPDY_MOD_SPDY_H_ */
--- /dev/null
+# Copyright 2010 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.
+
+vars = {
+ "chromium_trunk": "http://src.chromium.org/svn/trunk",
+ "chromium_revision": "@228939",
+ "chromium_deps_root": "src/third_party/chromium_deps",
+ "apr_src": "http://svn.apache.org/repos/asf/apr/apr/tags/1.4.2",
+ "apr_revision": "@head",
+ "aprutil_src": "http://svn.apache.org/repos/asf/apr/apr-util/tags/1.3.9",
+ "aprutil_revision": "@head",
+ "apache_httpd_src": "http://svn.apache.org/repos/asf/httpd/httpd/tags/2.2.15",
+ "apache_httpd_revision": "@head",
+ "mod_pagespeed_root": "http://modpagespeed.googlecode.com/svn/tags/0.10.19.5",
+ "mod_pagespeed_revision": "@head",
+}
+
+deps = {
+ # Fetch chromium DEPS so we can sync our other dependencies relative
+ # to it.
+ Var("chromium_deps_root"):
+ File(Var("chromium_trunk") + "/src/DEPS" + Var("chromium_revision")),
+
+ "src/net/instaweb/util":
+ Var("mod_pagespeed_root") + "/src/net/instaweb/util" +
+ Var("mod_pagespeed_revision"),
+
+ "src/testing":
+ Var("chromium_trunk") + "/src/testing" + Var("chromium_revision"),
+
+ "src/third_party/chromium/src/build":
+ Var("chromium_trunk") + "/src/build" + Var("chromium_revision"),
+
+ "src/third_party/chromium/src/base":
+ Var("chromium_trunk") + "/src/base" + Var("chromium_revision"),
+
+ "src/third_party/chromium/src/chrome/tools/build":
+ Var("chromium_trunk") + "/src/chrome/tools/build" +
+ Var("chromium_revision"),
+
+ "src/third_party/chromium/src/net/base":
+ Var("chromium_trunk") + "/src/net/base" + Var("chromium_revision"),
+
+ "src/third_party/chromium/src/net/socket":
+ Var("chromium_trunk") + "/src/net/socket" + Var("chromium_revision"),
+
+ "src/third_party/chromium/src/net/spdy":
+ Var("chromium_trunk") + "/src/net/spdy" + Var("chromium_revision"),
+
+ "src/third_party/chromium/src/net/tools/flip_server":
+ Var("chromium_trunk") + "/src/net/tools/flip_server" +
+ Var("chromium_revision"),
+
+ "src/build/linux":
+ Var("chromium_trunk") + "/src/build/linux" + Var("chromium_revision"),
+ "src/build/mac":
+ Var("chromium_trunk") + "/src/build/mac" + Var("chromium_revision"),
+ "src/build/win":
+ Var("chromium_trunk") + "/src/build/win" + Var("chromium_revision"),
+ "src/build/internal":
+ Var("chromium_trunk") + "/src/build/internal" + Var("chromium_revision"),
+
+ # lastchange.py changed its behavior at some point in a way that it
+ # stopped working for us. Thus we continue to sync just that file at
+ # a known good revision. We do not sync all of src/build/util so as
+ # to make sure that we don't accidentally depend on something else
+ # at that old revision.
+ "src/build/util":
+ File(Var("chromium_trunk") + "/src/build/util/lastchange.py" + "@90205"),
+
+ "src/third_party/apache/apr/src":
+ Var("apr_src") + Var("apr_revision"),
+
+ "src/third_party/apache/aprutil/src":
+ Var("aprutil_src") + Var("aprutil_revision"),
+
+ "src/third_party/apache/httpd/src/include":
+ Var("apache_httpd_src") + "/include" + Var("apache_httpd_revision"),
+
+ "src/third_party/apache/httpd/src/os":
+ Var("apache_httpd_src") + "/os" + Var("apache_httpd_revision"),
+
+ "src/third_party/modp_b64":
+ Var("chromium_trunk") + "/src/third_party/modp_b64" +
+ Var("chromium_revision"),
+
+ "src/third_party/protobuf":
+ (Var("chromium_trunk") + "/src/third_party/protobuf" +
+ Var("chromium_revision")),
+
+ "src/third_party/zlib":
+ Var("chromium_trunk") + "/src/third_party/zlib" + Var("chromium_revision"),
+
+ "src/testing/gmock": From(Var("chromium_deps_root")),
+ "src/testing/gtest": From(Var("chromium_deps_root")),
+ "src/tools/gyp": From(Var("chromium_deps_root")),
+}
+
+
+deps_os = {
+ "win": {
+ "src/third_party/cygwin": From(Var("chromium_deps_root")),
+ "src/third_party/python_26": From(Var("chromium_deps_root")),
+ },
+ "mac": {
+ },
+ "unix": {
+ },
+}
+
+
+include_rules = [
+ # Everybody can use some things.
+ "+base",
+ "+build",
+]
+
+
+# checkdeps.py shouldn't check include paths for files in these dirs:
+skip_child_includes = [
+ "testing",
+]
+
+
+hooks = [
+ {
+ # A change to a .gyp, .gypi, or to GYP itself should run the generator.
+ "pattern": ".",
+ "action": ["python", "src/build/gyp_chromium"],
+ },
+]
--- /dev/null
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Base was branched from the chromium version to reduce the number of
+# dependencies of this package. Specifically, we would like to avoid
+# depending on the chrome directory, which contains the chrome version
+# and branding information.
+# TODO: push this refactoring to chronium trunk.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ 'chromium_root': '<(DEPTH)/third_party/chromium/src',
+ },
+ 'targets': [
+ {
+ 'target_name': 'base',
+ 'type': '<(component)',
+ 'dependencies': [
+ '<(DEPTH)/third_party/modp_b64/modp_b64.gyp:modp_b64',
+ '<(chromium_root)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ ],
+ 'sources': [
+ '<(chromium_root)/base/at_exit.cc',
+ '<(chromium_root)/base/atomicops_internals_x86_gcc.cc',
+ '<(chromium_root)/base/base_switches.cc',
+ '<(chromium_root)/base/callback_internal.cc',
+ '<(chromium_root)/base/command_line.cc',
+ '<(chromium_root)/base/debug/alias.cc',
+ '<(chromium_root)/base/debug/debugger.cc',
+ '<(chromium_root)/base/debug/debugger_posix.cc',
+ '<(chromium_root)/base/debug/debugger_win.cc',
+ '<(chromium_root)/base/debug/stack_trace.cc',
+ '<(chromium_root)/base/debug/stack_trace_posix.cc',
+ '<(chromium_root)/base/debug/stack_trace_win.cc',
+ '<(chromium_root)/base/files/file_path.cc',
+ '<(chromium_root)/base/files/file_path_constants.cc',
+ '<(chromium_root)/base/lazy_instance.cc',
+ '<(chromium_root)/base/location.cc',
+ '<(chromium_root)/base/logging.cc',
+ '<(chromium_root)/base/logging_win.cc',
+ '<(chromium_root)/base/mac/foundation_util.mm',
+ '<(chromium_root)/base/memory/ref_counted.cc',
+ '<(chromium_root)/base/memory/singleton.cc',
+ '<(chromium_root)/base/metrics/stats_counters.cc',
+ 'metrics/stats_table.cc',
+ '<(chromium_root)/base/pickle.cc',
+ '<(chromium_root)/base/process/process_handle_linux.cc',
+ '<(chromium_root)/base/process/process_handle_mac.cc',
+ '<(chromium_root)/base/process/process_handle_posix.cc',
+ '<(chromium_root)/base/process/process_handle_win.cc',
+ '<(chromium_root)/base/profiler/alternate_timer.cc',
+ '<(chromium_root)/base/profiler/tracked_time.cc',
+ '<(chromium_root)/base/safe_strerror_posix.cc',
+ '<(chromium_root)/base/strings/string16.cc',
+ '<(chromium_root)/base/strings/string16.h',
+ '<(chromium_root)/base/strings/string_number_conversions.cc',
+ '<(chromium_root)/base/strings/string_piece.cc',
+ '<(chromium_root)/base/strings/string_split.cc',
+ '<(chromium_root)/base/strings/string_util.cc',
+ '<(chromium_root)/base/strings/string_util_constants.cc',
+ '<(chromium_root)/base/strings/stringprintf.cc',
+ '<(chromium_root)/base/strings/sys_string_conversions_posix.cc',
+ '<(chromium_root)/base/strings/sys_string_conversions_mac.mm',
+ '<(chromium_root)/base/strings/sys_string_conversions_win.cc',
+ '<(chromium_root)/base/strings/utf_string_conversion_utils.cc',
+ '<(chromium_root)/base/strings/utf_string_conversions.cc',
+ '<(chromium_root)/base/synchronization/condition_variable_posix.cc',
+ '<(chromium_root)/base/synchronization/condition_variable_win.cc',
+ '<(chromium_root)/base/synchronization/lock.cc',
+ '<(chromium_root)/base/synchronization/lock_impl_posix.cc',
+ '<(chromium_root)/base/synchronization/lock_impl_win.cc',
+ '<(chromium_root)/base/synchronization/waitable_event_posix.cc',
+ '<(chromium_root)/base/synchronization/waitable_event_win.cc',
+ '<(chromium_root)/base/third_party/dmg_fp/g_fmt.cc',
+ '<(chromium_root)/base/third_party/dmg_fp/dtoa_wrapper.cc',
+ '<(chromium_root)/base/third_party/icu/icu_utf.cc',
+ '<(chromium_root)/base/third_party/nspr/prtime.cc',
+ '<(chromium_root)/base/threading/platform_thread_mac.mm',
+ '<(chromium_root)/base/threading/platform_thread_linux.cc',
+ '<(chromium_root)/base/threading/platform_thread_posix.cc',
+ '<(chromium_root)/base/threading/platform_thread_win.cc',
+ '<(chromium_root)/base/threading/thread_collision_warner.cc',
+ '<(chromium_root)/base/threading/thread_id_name_manager.cc',
+ '<(chromium_root)/base/threading/thread_local_posix.cc',
+ '<(chromium_root)/base/threading/thread_local_win.cc',
+ '<(chromium_root)/base/threading/thread_local_storage_posix.cc',
+ '<(chromium_root)/base/threading/thread_local_storage_win.cc',
+ '<(chromium_root)/base/threading/thread_restrictions.cc',
+ '<(chromium_root)/base/time/time.cc',
+ '<(chromium_root)/base/time/time_mac.cc',
+ '<(chromium_root)/base/time/time_posix.cc',
+ '<(chromium_root)/base/time/time_win.cc',
+ '<(chromium_root)/base/tracked_objects.cc',
+ '<(chromium_root)/base/vlog.cc',
+ '<(chromium_root)/base/win/registry.cc',
+ '<(chromium_root)/base/win/win_util.cc',
+ '<(chromium_root)/base/win/windows_version.cc',
+ ],
+ 'include_dirs': [
+ '<(chromium_root)',
+ '<(DEPTH)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(chromium_root)',
+ '<(DEPTH)',
+ ],
+ },
+ 'conditions': [
+ ['OS != "win"', {
+ 'sources/': [ ['exclude', '^win/'] ],
+ }],
+ [ 'OS == "win"', {
+ 'sources!': [
+ '<(chromium_root)/base/string16.cc',
+ ],
+ }],
+ ['OS == "linux"', {
+ 'cflags': [
+ '-Wno-write-strings',
+ '-Wno-error',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ # We need rt for clock_gettime().
+ '-lrt',
+ ],
+ },
+ }],
+ ],
+ },
+ ],
+}
--- /dev/null
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A stubbed version of stats_table.cc that doesn't do anything. These
+// functions must be defined in order to link with code that updates
+// stats (such as spdy_framer.cc).
+
+#include "base/metrics/stats_table.h"
+
+namespace base {
+
+StatsTable* StatsTable::current() { return NULL; }
+
+int StatsTable::RegisterThread(const std::string& name) {
+ return 0;
+}
+
+int StatsTable::GetSlot() const {
+ return 0;
+}
+
+int StatsTable::FindCounter(const std::string& name) {
+ return 0;
+}
+
+int* StatsTable::GetLocation(int counter_id, int slot_id) const {
+ return NULL;
+}
+
+} // namespace base
--- /dev/null
+# Copyright 2010 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'All',
+ 'type': 'none',
+ 'xcode_create_dependents_test_runner': 1,
+ 'dependencies': [
+ '../base/base.gyp:*',
+ '../mod_spdy/mod_spdy.gyp:*',
+ '../net/net.gyp:*',
+ '../third_party/mod_diagnostics/mod_diagnostics.gyp:*',
+ 'install.gyp:*',
+ ],} ]
+}
--- /dev/null
+#include "third_party/chromium/src/build/build_config.h"
--- /dev/null
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'version_py_path': 'version.py',
+ 'mod_spdy_path': '<(DEPTH)/mod_spdy',
+ 'version_path': '<(mod_spdy_path)/common/VERSION',
+ 'version_h_in_path': '<(mod_spdy_path)/common/version.h.in',
+ 'version_h_path': '<(SHARED_INTERMEDIATE_DIR)/mod_spdy/common/version.h',
+ 'lastchange_out_path': '<(SHARED_INTERMEDIATE_DIR)/build/LASTCHANGE',
+ },
+ 'targets': [
+ {
+ 'target_name': 'lastchange',
+ 'type': 'none',
+ 'variables': {
+ 'default_lastchange_path': '../LASTCHANGE.in',
+ },
+ 'actions': [
+ {
+ 'action_name': 'lastchange',
+ 'inputs': [
+ # Note: <(default_lastchange_path) is optional,
+ # so it doesn't show up in inputs.
+ 'util/lastchange.py',
+ ],
+ 'outputs': [
+ '<(lastchange_out_path).always',
+ '<(lastchange_out_path)',
+ ],
+ 'action': [
+ 'python', '<@(_inputs)',
+ '-o', '<(lastchange_out_path)',
+ '-d', '<(default_lastchange_path)',
+ ],
+ 'message': 'Extracting last change to <(lastchange_out_path)',
+ 'process_outputs_as_sources': '1',
+ },
+ ],
+ },
+ {
+ 'target_name': 'mod_spdy_version_header',
+ 'type': 'none',
+ 'dependencies': [
+ 'lastchange',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'version_header',
+ 'inputs': [
+ '<(version_path)',
+ '<(lastchange_out_path)',
+ '<(version_h_in_path)',
+ ],
+ 'outputs': [
+ '<(version_h_path)',
+ ],
+ 'action': [
+ 'python',
+ '<(version_py_path)',
+ '-f', '<(version_path)',
+ '-f', '<(lastchange_out_path)',
+ '<(version_h_in_path)',
+ '<@(_outputs)',
+ ],
+ 'message': 'Generating version header file: <@(_outputs)',
+ },
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ },
+ },
+ ]
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
--- /dev/null
+# Copyright 2010 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.
+#
+# mod_spdy common gyp configuration.
+{
+ 'variables': {
+ 'library%': 'static_library',
+
+ # Don't use the gold linker:
+ 'linux_use_gold_binary': 0,
+ 'linux_use_gold_flags': 0,
+
+ # Don't use the system Apache dev files by default:
+ 'use_system_apache_dev%': 0,
+
+ # Turn these off to prevent Chromium's build config from bothering us about
+ # things we don't care about for mod_spdy:
+ 'clang_use_chrome_plugins': 0,
+ 'incremental_chrome_dll': 0,
+ 'use_official_google_api_keys': 0,
+ },
+
+ # Import Chromium's common.gypi to inherit their build configuration.
+ 'includes': [
+ '../third_party/chromium/src/build/common.gypi',
+ ],
+
+ # Modify the Chromium configuration as needed:
+ 'target_defaults': {
+ # Make sure our shadow view of chromium source is available to
+ # targets that don't explicitly declare their dependencies and
+ # assume chromium source headers are available from the root
+ # (third_party/modp_b64 is one such target).
+ 'include_dirs': [
+ '<(DEPTH)/third_party/chromium/src',
+ ],
+ },
+}
--- /dev/null
+#!/usr/bin/python
+
+# Copyright 2010 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.
+
+# This script is wrapper for the Chromium version of compiler_version.py.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party', 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'build', 'compiler_version.py'))
--- /dev/null
+#!/usr/bin/python
+
+# Copyright 2010 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.
+
+# This script is wrapper for the Chromium version of dir_exists.py.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party', 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'build', 'dir_exists.py'))
--- /dev/null
+# Copyright 2010 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.
+
+# Chromium expects this file to be here, but for our purposes, it
+# doesn't need to actually do anything.
+
+{}
--- /dev/null
+#!/usr/bin/python
+
+# Copyright 2010 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.
+
+# This script is wrapper for the Chromium version of gyp_chromium.
+
+import os
+import sys
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party', 'chromium', 'src'))
+
+# The Chromium gyp_chromium defaults to ninja on linux. We want to default to
+# make instead.
+if sys.platform.startswith('linux') and not os.environ.get('GYP_GENERATORS'):
+ os.environ['GYP_GENERATORS'] = 'make'
+
+execfile(os.path.join(chrome_src, 'build', 'gyp_chromium'))
--- /dev/null
+#!/usr/bin/python
+
+# Copyright 2010 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.
+
+# This script is wrapper for the Chromium version of gyp_helper.py.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party', 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'build', 'gyp_helper.py'))
--- /dev/null
+# Copyright (c) 2010 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'install_path': '<(DEPTH)/install',
+ 'version_py_path': '<(DEPTH)/build/version.py',
+ 'version_path': '<(DEPTH)/mod_spdy/common/VERSION',
+ 'lastchange_path': '<(SHARED_INTERMEDIATE_DIR)/build/LASTCHANGE',
+ 'branding_path': '<(install_path)/common/BRANDING',
+ },
+ 'conditions': [
+ ['OS=="linux"', {
+ 'variables': {
+ 'version' : '<!(python <(version_py_path) -f <(version_path) -t "@MAJOR@.@MINOR@.@BUILD@.@PATCH@")',
+ 'revision' : '<!(python <(DEPTH)/build/util/lastchange.py | cut -d "=" -f 2)',
+ 'packaging_files_common': [
+ '<(install_path)/common/apt.include',
+ '<(install_path)/common/mod-spdy.info',
+ '<(install_path)/common/installer.include',
+ '<(install_path)/common/repo.cron',
+ '<(install_path)/common/rpm.include',
+ '<(install_path)/common/rpmrepo.cron',
+ '<(install_path)/common/updater',
+ '<(install_path)/common/variables.include',
+ '<(install_path)/common/BRANDING',
+ '<(install_path)/common/spdy.load.template',
+ '<(install_path)/common/spdy.conf.template',
+ '<(install_path)/common/ssl.load.template',
+ ],
+ 'packaging_files_deb': [
+ '<(install_path)/debian/build.sh',
+ '<(install_path)/debian/changelog.template',
+ '<(install_path)/debian/conffiles.template',
+ '<(install_path)/debian/control.template',
+ '<(install_path)/debian/postinst',
+ '<(install_path)/debian/postrm',
+ '<(install_path)/debian/preinst',
+ '<(install_path)/debian/prerm',
+ ],
+ 'packaging_files_rpm': [
+ '<(install_path)/rpm/build.sh',
+ '<(install_path)/rpm/mod-spdy.spec.template',
+ ],
+ 'packaging_files_binaries': [
+ '<(PRODUCT_DIR)/libmod_spdy.so',
+ ],
+ 'flock_bash': ['flock', '--', '/tmp/linux_package_lock', 'bash'],
+ 'deb_build': '<(PRODUCT_DIR)/install/debian/build.sh',
+ 'rpm_build': '<(PRODUCT_DIR)/install/rpm/build.sh',
+ 'deb_cmd': ['<@(flock_bash)', '<(deb_build)', '-o' '<(PRODUCT_DIR)',
+ '-b', '<(PRODUCT_DIR)', '-a', '<(target_arch)'],
+ 'rpm_cmd': ['<@(flock_bash)', '<(rpm_build)', '-o' '<(PRODUCT_DIR)',
+ '-b', '<(PRODUCT_DIR)', '-a', '<(target_arch)'],
+ 'conditions': [
+ ['target_arch=="ia32"', {
+ 'deb_arch': 'i386',
+ 'rpm_arch': 'i386',
+ }],
+ ['target_arch=="x64"', {
+ 'deb_arch': 'amd64',
+ 'rpm_arch': 'x86_64',
+ }],
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'linux_installer_configs',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ # Add these files to the build output so the build archives will be
+ # "hermetic" for packaging.
+ 'copies': [
+ {
+ 'destination': '<(PRODUCT_DIR)/install/debian/',
+ 'files': [
+ '<@(packaging_files_deb)',
+ ]
+ },
+ {
+ 'destination': '<(PRODUCT_DIR)/install/rpm/',
+ 'files': [
+ '<@(packaging_files_rpm)',
+ ]
+ },
+ {
+ 'destination': '<(PRODUCT_DIR)/install/common/',
+ 'files': [
+ '<@(packaging_files_common)',
+ ]
+ },
+ {
+ 'destination': '<(PRODUCT_DIR)/',
+ 'files': [
+ '<(DEPTH)/mod_ssl.so',
+ ]
+ },
+ ],
+ 'actions': [
+ {
+ 'action_name': 'save_build_info',
+ 'inputs': [
+ '<(branding_path)',
+ '<(version_path)',
+ '<(lastchange_path)',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/installer/version.txt',
+ ],
+ # Just output the default version info variables.
+ 'action': [
+ 'python', '<(version_py_path)',
+ '-f', '<(branding_path)',
+ '-f', '<(version_path)',
+ '-f', '<(lastchange_path)',
+ '-o', '<@(_outputs)'
+ ],
+ },
+ ],
+ },
+ {
+ 'target_name': 'linux_packages',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ 'dependencies': [
+ 'linux_package_deb',
+ 'linux_package_rpm',
+ ],
+ },
+ {
+ 'target_name': 'linux_package_deb',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ 'dependencies': [
+ '<(DEPTH)/mod_spdy/mod_spdy.gyp:mod_spdy',
+ 'linux_installer_configs',
+ ],
+ 'actions': [
+ {
+ 'variables': {
+ 'channel': 'beta',
+ },
+ 'action_name': 'deb_package_<(channel)',
+ 'process_outputs_as_sources': 1,
+ 'inputs': [
+ '<(deb_build)',
+ '<@(packaging_files_binaries)',
+ '<@(packaging_files_common)',
+ '<@(packaging_files_deb)',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/mod-spdy-<(channel)_<(version)-r<(revision)_<(deb_arch).deb',
+ ],
+ 'action': [ '<@(deb_cmd)', '-c', '<(channel)', ],
+ },
+ ],
+ },
+ {
+ 'target_name': 'linux_package_rpm',
+ 'suppress_wildcard': 1,
+ 'type': 'none',
+ 'dependencies': [
+ '<(DEPTH)/mod_spdy/mod_spdy.gyp:mod_spdy',
+ 'linux_installer_configs',
+ ],
+ 'actions': [
+ {
+ 'variables': {
+ 'channel': 'beta',
+ },
+ 'action_name': 'rpm_package_<(channel)',
+ 'process_outputs_as_sources': 1,
+ 'inputs': [
+ '<(rpm_build)',
+ '<(PRODUCT_DIR)/install/rpm/mod-spdy.spec.template',
+ '<@(packaging_files_binaries)',
+ '<@(packaging_files_common)',
+ '<@(packaging_files_rpm)',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/mod-spdy-<(channel)-<(version)-r<(revision).<(rpm_arch).rpm',
+ ],
+ 'action': [ '<@(rpm_cmd)', '-c', '<(channel)', ],
+ },
+ ],
+ },
+ ],
+ },{
+ 'targets': [
+ ],
+ }],
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<VisualStudioToolFile
+ Name="Output DLL copy"
+ Version="8.00"
+ >
+ <Rules>
+ <CustomBuildRule
+ Name="Output DLL copy"
+ CommandLine="xcopy /R /C /Y $(InputPath) $(OutDir)"
+ Outputs="$(OutDir)\$(InputFileName)"
+ FileExtensions="*.dll"
+ >
+ <Properties>
+ </Properties>
+ </CustomBuildRule>
+ </Rules>
+</VisualStudioToolFile>
--- /dev/null
+{
+ 'conditions': [
+ # Handle build types.
+ ['buildtype=="Dev"', {
+ 'includes': ['internal/release_impl.gypi'],
+ }],
+ ['buildtype=="Official"', {
+ 'includes': ['internal/release_impl_official.gypi'],
+ }],
+ # TODO(bradnelson): may also need:
+ # checksenabled
+ # coverage
+ # dom_stats
+ # pgo_instrument
+ # pgo_optimize
+ # purify
+ ],
+}
+
--- /dev/null
+#!/usr/bin/python
+
+# Copyright 2010 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.
+
+# This script is wrapper for the Chromium version of compiler_version.py.
+
+import os
+
+script_dir = os.path.dirname(__file__)
+chrome_src = os.path.normpath(os.path.join(script_dir, os.pardir, 'third_party',
+ 'chromium', 'src'))
+
+execfile(os.path.join(chrome_src, 'chrome', 'tools', 'build', 'version.py'))
--- /dev/null
+#!/bin/bash
+#
+# This script builds mod_ssl.so for Apache 2.2.x, with SSL NPN
+# support.
+#
+# NPN is not yet supported in Apache HTTPD mod_ssl. A patch has been
+# submitted to Apache to enable NPN in mod_ssl:
+# https://issues.apache.org/bugzilla/show_bug.cgi?id=52210
+#
+# Thus, we download the 1.0.1 release of OpenSSL and the most recent
+# release of Apache 2.2, and apply a patch to enable NPN support in
+# Apache mod_ssl.
+#
+# We currently statically link OpenSSL with mod_ssl, which results in
+# a large (several megabyte) mod_ssl.so. If you prefer, you can
+# install NPN-enabled OpenSSL as a shared library system-wide, by
+# building OpenSSL like so:
+#
+# ./config shared -fPIC # -fPIC is only needed on some architectures
+# make
+# sudo make install
+#
+# And Apache like so (after applying the NPN patch):
+#
+# ./configure --enable-ssl=shared
+# make
+
+MODSSL_SO_DESTPATH=$(pwd)/mod_ssl.so
+
+if [ -f $MODSSL_SO_DESTPATH ]; then
+ echo "mod_ssl already exists at $MODSSL_SO_DESTPATH. Please remove."
+ exit 1
+fi
+
+if [ -z "$BUILDROOT" ]; then
+ BUILDROOT=$(mktemp -d)
+ REMOVE_BUILDROOT=1
+else
+ REMOVE_BUILDROOT=0
+fi
+
+if [ ! -d "$BUILDROOT" ]; then
+ echo "Not a directory: $BUILDROOT"
+ exit 1
+fi
+
+# Convert BUILDROOT to an absolute path.
+BUILDROOT="$(cd $(dirname $BUILDROOT); pwd)/$(basename $BUILDROOT)"
+echo "Using buildroot: $BUILDROOT"
+echo ""
+
+function do_cleanup {
+ echo ""
+ echo "Build aborted."
+ if [ $REMOVE_BUILDROOT -eq 1 ]; then
+ echo -n "Cleaning up ... "
+ rm -rf "$BUILDROOT"
+ echo "done"
+ fi
+ exit 1
+}
+
+trap 'do_cleanup' SIGINT SIGTERM
+
+PROGRESS_DIR=$BUILDROOT/progress
+mkdir -p $PROGRESS_DIR
+if [ $? -ne 0 ]; then
+ do_cleanup
+fi
+
+function download_file {
+ if [ ! -f "$PROGRESS_DIR/$2.downloaded" ]; then
+ echo "Downloading $1"
+ curl -f -# "$1" -o $2 || do_cleanup
+ if [[ $(md5sum $2 | cut -d\ -f1) != $3 ]]; then
+ echo "md5sum mismatch for $2"
+ do_cleanup
+ fi
+ touch "$PROGRESS_DIR/$2.downloaded"
+ else
+ echo "Already downloaded $1"
+ fi
+}
+
+function uncompress_file {
+ if [ ! -f "$PROGRESS_DIR/$1.uncompressed" ]; then
+ echo -n "Uncompressing $1 ... "
+ tar xzf $1 || do_cleanup
+ echo "done"
+ touch "$PROGRESS_DIR/$1.uncompressed"
+ else
+ echo "Already uncompressed $1"
+ fi
+}
+
+OPENSSL_SRC_TGZ_URL="https://www.openssl.org/source/openssl-1.0.1g.tar.gz"
+APACHE_HTTPD_SRC_TGZ_URL="https://archive.apache.org/dist/httpd/httpd-2.2.27.tar.gz"
+APACHE_HTTPD_MODSSL_NPN_PATCH_PATH="$(dirname $0)/scripts/mod_ssl_with_npn.patch"
+
+OPENSSL_SRC_TGZ=$(basename $OPENSSL_SRC_TGZ_URL)
+APACHE_HTTPD_SRC_TGZ=$(basename $APACHE_HTTPD_SRC_TGZ_URL)
+APACHE_HTTPD_MODSSL_NPN_PATCH="mod_ssl_npn.patch"
+
+OPENSSL_SRC_ROOT=${OPENSSL_SRC_TGZ%.tar.gz}
+OPENSSL_INST_ROOT=${OPENSSL_SRC_ROOT}_install
+APACHE_HTTPD_SRC_ROOT=${APACHE_HTTPD_SRC_TGZ%.tar.gz}
+
+OPENSSL_BUILDLOG=$(mktemp -p /tmp openssl_buildlog.XXXXXXXXXX)
+APACHE_HTTPD_BUILDLOG=$(mktemp -p /tmp httpd_buildlog.XXXXXXXXXX)
+
+cp $APACHE_HTTPD_MODSSL_NPN_PATCH_PATH $BUILDROOT/$APACHE_HTTPD_MODSSL_NPN_PATCH
+
+pushd $BUILDROOT >/dev/null
+
+download_file $OPENSSL_SRC_TGZ_URL $OPENSSL_SRC_TGZ de62b43dfcd858e66a74bee1c834e959
+download_file $APACHE_HTTPD_SRC_TGZ_URL $APACHE_HTTPD_SRC_TGZ 148eb08e731916a43a33a6ffa25f17c0
+
+echo ""
+
+uncompress_file $OPENSSL_SRC_TGZ
+uncompress_file $APACHE_HTTPD_SRC_TGZ
+
+if [ ! -f "$PROGRESS_DIR/modssl_patched" ]; then
+ pushd $APACHE_HTTPD_SRC_ROOT >/dev/null
+ echo "Applying Apache mod_ssl NPN patch ... "
+ patch -p0 < $BUILDROOT/$APACHE_HTTPD_MODSSL_NPN_PATCH
+ if [ $? -ne 0 ]; then
+ echo "Failed to patch."
+ do_cleanup
+ fi
+ echo "done"
+ popd >/dev/null # $APACHE_HTTPD_SRC_ROOT
+ touch "$PROGRESS_DIR/modssl_patched"
+else
+ echo "Already applied Apache mod_ssl NPN patch."
+fi
+
+echo ""
+
+if [ ! -f "$PROGRESS_DIR/openssl_configured" ]; then
+ pushd $OPENSSL_SRC_ROOT >/dev/null
+ echo -n "Configuring OpenSSL ... "
+ ./config no-shared -fPIC --openssldir=$BUILDROOT/$OPENSSL_INST_ROOT >> $OPENSSL_BUILDLOG
+ if [ $? -ne 0 ]; then
+ echo "Failed. Build log at $OPENSSL_BUILDLOG."
+ do_cleanup
+ fi
+ echo "done"
+ popd >/dev/null # $OPENSSL_SRC_ROOT
+ touch "$PROGRESS_DIR/openssl_configured"
+else
+ echo "Already configured OpenSSL."
+fi
+
+if [ ! -f "$PROGRESS_DIR/openssl_built" ]; then
+ pushd $OPENSSL_SRC_ROOT >/dev/null
+ echo -n "Building OpenSSL (this may take a while) ... "
+ make install >> $OPENSSL_BUILDLOG 2>&1
+ if [ $? -ne 0 ]; then
+ echo "Failed. Build log at $OPENSSL_BUILDLOG."
+ do_cleanup
+ fi
+ # A hacky fix that helps things build on CentOS:
+ if grep -q CentOS /etc/issue; then
+ sed --in-place 's/^Libs\.private: -ldl$/& -lcrypto/' \
+ $BUILDROOT/$OPENSSL_INST_ROOT/lib/pkgconfig/openssl.pc
+ fi
+ echo "done"
+ popd >/dev/null # $OPENSSL_SRC_ROOT
+ touch "$PROGRESS_DIR/openssl_built"
+else
+ echo "Already built OpenSSL."
+fi
+
+rm -f "$OPENSSL_BUILDLOG"
+
+echo ""
+
+if [ ! -f "$PROGRESS_DIR/modssl_configured" ]; then
+ pushd $APACHE_HTTPD_SRC_ROOT >/dev/null
+ echo -n "Configuring Apache mod_ssl ... "
+
+ # OpenSSL, as of version 1.0.1, changed its pkg-config file to list
+ # its dependent libraries in Libs.private. Prior to this, dependent
+ # libraries were listed in Libs. This change in 1.0.1 is the right
+ # thing for OpenSSL, but it breaks the Apache 2.2.x configure when
+ # linking statically against OpenSSL, since it assumes that all
+ # dependent libs are provided in the pkg config Libs directive. We
+ # run a search-replace on the configure script to tell it to include
+ # not only libraries in Libs, but also those in Libs.private:
+ mv configure configure.bak
+ sed 's/--libs-only-l openssl/--libs-only-l --static openssl/' configure.bak > configure
+ chmod --reference=configure.bak configure
+
+ ./configure --enable-ssl=shared --with-ssl=$BUILDROOT/$OPENSSL_INST_ROOT >> $APACHE_HTTPD_BUILDLOG
+ if [ $? -ne 0 ]; then
+ echo "Failed. Build log at $APACHE_HTTPD_BUILDLOG."
+ do_cleanup
+ fi
+ echo "done"
+ popd >/dev/null # $APACHE_HTTPD_SRC_ROOT
+ touch "$PROGRESS_DIR/modssl_configured"
+else
+ echo "Already configured Apache mod_ssl."
+fi
+
+if [ ! -f "$PROGRESS_DIR/modssl_built" ]; then
+ pushd $APACHE_HTTPD_SRC_ROOT >/dev/null
+ echo -n "Building Apache mod_ssl (this may take a while) ... "
+ make >> $APACHE_HTTPD_BUILDLOG 2>&1
+ if [ $? -ne 0 ]; then
+ echo "Failed. Build log at $APACHE_HTTPD_BUILDLOG."
+ do_cleanup
+ fi
+ echo "done"
+ popd >/dev/null # $APACHE_HTTPD_SRC_ROOT
+ touch "$PROGRESS_DIR/modssl_built"
+else
+ echo "Already built Apache mod_ssl."
+fi
+
+rm -f "$APACHE_HTTPD_BUILDLOG"
+
+popd >/dev/null # $BUILDROOT
+
+MODSSL_SO_SRCPATH=$(find $BUILDROOT/$APACHE_HTTPD_SRC_ROOT -name mod_ssl.so)
+if [ $(echo $MODSSL_SO_SRCPATH | wc -l) -ne 1 ]; then
+ echo "Found multiple mod_ssl.so's:"
+ echo $MODSSL_SO_SRCPATH
+ do_cleanup
+fi
+
+cp $MODSSL_SO_SRCPATH $MODSSL_SO_DESTPATH
+
+if [ $REMOVE_BUILDROOT -eq 1 ]; then
+ rm -rf "$BUILDROOT"
+fi
+
+echo ""
+echo "Generated mod_ssl.so at $MODSSL_SO_DESTPATH."
--- /dev/null
+COMPANY_FULLNAME=Google Inc.
+COMPANY_SHORTNAME=Google Inc.
+PRODUCT_FULLNAME=mod_spdy
+PRODUCT_SHORTNAME=mod_spdy
+COPYRIGHT=Copyright (C) 2012.
--- /dev/null
+@@include@@variables.include
+
+APT_GET="`which apt-get 2> /dev/null`"
+APT_CONFIG="`which apt-config 2> /dev/null`"
+
+SOURCES_PREAMBLE="### THIS FILE IS AUTOMATICALLY CONFIGURED ###
+# You may comment out this entry, but any other modifications may be lost.\n"
+
+# Parse apt configuration and return requested variable value.
+apt_config_val() {
+ APTVAR="$1"
+ if [ -x "$APT_CONFIG" ]; then
+ "$APT_CONFIG" dump | sed -e "/^$APTVAR /"'!d' -e "s/^$APTVAR \"\(.*\)\".*/\1/"
+ fi
+}
+
+# Install the repository signing key (see also:
+# http://www.google.com/linuxrepositories/aboutkey.html)
+install_key() {
+ APT_KEY="`which apt-key 2> /dev/null`"
+ if [ -x "$APT_KEY" ]; then
+ "$APT_KEY" add - >/dev/null 2>&1 <<KEYDATA
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.2.2 (GNU/Linux)
+
+mQGiBEXwb0YRBADQva2NLpYXxgjNkbuP0LnPoEXruGmvi3XMIxjEUFuGNCP4Rj/a
+kv2E5VixBP1vcQFDRJ+p1puh8NU0XERlhpyZrVMzzS/RdWdyXf7E5S8oqNXsoD1z
+fvmI+i9b2EhHAA19Kgw7ifV8vMa4tkwslEmcTiwiw8lyUl28Wh4Et8SxzwCggDcA
+feGqtn3PP5YAdD0km4S4XeMEAJjlrqPoPv2Gf//tfznY2UyS9PUqFCPLHgFLe80u
+QhI2U5jt6jUKN4fHauvR6z3seSAsh1YyzyZCKxJFEKXCCqnrFSoh4WSJsbFNc4PN
+b0V0SqiTCkWADZyLT5wll8sWuQ5ylTf3z1ENoHf+G3um3/wk/+xmEHvj9HCTBEXP
+78X0A/0Tqlhc2RBnEf+AqxWvM8sk8LzJI/XGjwBvKfXe+l3rnSR2kEAvGzj5Sg0X
+4XmfTg4Jl8BNjWyvm2Wmjfet41LPmYJKsux3g0b8yzQxeOA4pQKKAU3Z4+rgzGmf
+HdwCG5MNT2A5XxD/eDd+L4fRx0HbFkIQoAi1J3YWQSiTk15fw7RMR29vZ2xlLCBJ
+bmMuIExpbnV4IFBhY2thZ2UgU2lnbmluZyBLZXkgPGxpbnV4LXBhY2thZ2VzLWtl
+eW1hc3RlckBnb29nbGUuY29tPohjBBMRAgAjAhsDBgsJCAcDAgQVAggDBBYCAwEC
+HgECF4AFAkYVdn8CGQEACgkQoECDD3+sWZHKSgCfdq3HtNYJLv+XZleb6HN4zOcF
+AJEAniSFbuv8V5FSHxeRimHx25671az+uQINBEXwb0sQCACuA8HT2nr+FM5y/kzI
+A51ZcC46KFtIDgjQJ31Q3OrkYP8LbxOpKMRIzvOZrsjOlFmDVqitiVc7qj3lYp6U
+rgNVaFv6Qu4bo2/ctjNHDDBdv6nufmusJUWq/9TwieepM/cwnXd+HMxu1XBKRVk9
+XyAZ9SvfcW4EtxVgysI+XlptKFa5JCqFM3qJllVohMmr7lMwO8+sxTWTXqxsptJo
+pZeKz+UBEEqPyw7CUIVYGC9ENEtIMFvAvPqnhj1GS96REMpry+5s9WKuLEaclWpd
+K3krttbDlY1NaeQUCRvBYZ8iAG9YSLHUHMTuI2oea07Rh4dtIAqPwAX8xn36JAYG
+2vgLAAMFB/wKqaycjWAZwIe98Yt0qHsdkpmIbarD9fGiA6kfkK/UxjL/k7tmS4Vm
+CljrrDZkPSQ/19mpdRcGXtb0NI9+nyM5trweTvtPw+HPkDiJlTaiCcx+izg79Fj9
+KcofuNb3lPdXZb9tzf5oDnmm/B+4vkeTuEZJ//IFty8cmvCpzvY+DAz1Vo9rA+Zn
+cpWY1n6z6oSS9AsyT/IFlWWBZZ17SpMHu+h4Bxy62+AbPHKGSujEGQhWq8ZRoJAT
+G0KSObnmZ7FwFWu1e9XFoUCt0bSjiJWTIyaObMrWu/LvJ3e9I87HseSJStfw6fki
+5og9qFEkMrIrBCp3QGuQWBq/rTdMuwNFiEkEGBECAAkFAkXwb0sCGwwACgkQoECD
+D3+sWZF/WACfeNAu1/1hwZtUo1bR+MWiCjpvHtwAnA1R3IHqFLQ2X3xJ40XPuAyY
+/FJG
+=Quqp
+-----END PGP PUBLIC KEY BLOCK-----
+KEYDATA
+ fi
+}
+
+# Set variables for the locations of the apt sources lists.
+find_apt_sources() {
+ APTDIR=$(apt_config_val Dir)
+ APTETC=$(apt_config_val 'Dir::Etc')
+ APT_SOURCES="$APTDIR$APTETC$(apt_config_val 'Dir::Etc::sourcelist')"
+ APT_SOURCESDIR="$APTDIR$APTETC$(apt_config_val 'Dir::Etc::sourceparts')"
+}
+
+# Update the Google repository if it's not set correctly.
+# Note: this doesn't necessarily enable the repository, it just makes sure the
+# correct settings are available in the sources list.
+# Returns:
+# 0 - no update necessary
+# 2 - error
+update_bad_sources() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+
+ SOURCELIST="$APT_SOURCESDIR/@@PACKAGE@@.list"
+ # Don't do anything if the file isn't there, since that probably means the
+ # user disabled it.
+ if [ ! -r "$SOURCELIST" ]; then
+ return 0
+ fi
+
+ # Basic check for active configurations (non-blank, non-comment lines).
+ ACTIVECONFIGS=$(grep -v "^[[:space:]]*\(#.*\)\?$" "$SOURCELIST" 2>/dev/null)
+
+ # Check if the correct repository configuration is in there.
+ REPOMATCH=$(grep "^[[:space:]#]*\b$REPOCONFIG\b" "$SOURCELIST" \
+ 2>/dev/null)
+
+ # Check if the correct repository is disabled.
+ MATCH_DISABLED=$(echo "$REPOMATCH" | grep "^[[:space:]]*#" 2>/dev/null)
+
+ # Now figure out if we need to fix things.
+ BADCONFIG=1
+ if [ "$REPOMATCH" ]; then
+ # If it's there and active, that's ideal, so nothing to do.
+ if [ ! "$MATCH_DISABLED" ]; then
+ BADCONFIG=0
+ else
+ # If it's not active, but neither is anything else, that's fine too.
+ if [ ! "$ACTIVECONFIGS" ]; then
+ BADCONFIG=0
+ fi
+ fi
+ fi
+
+ if [ $BADCONFIG -eq 0 ]; then
+ return 0
+ fi
+
+ # At this point, either the correct configuration is completely missing, or
+ # the wrong configuration is active. In that case, just abandon the mess and
+ # recreate the file with the correct configuration. If there were no active
+ # configurations before, create the new configuration disabled.
+ DISABLE=""
+ if [ ! "$ACTIVECONFIGS" ]; then
+ DISABLE="#"
+ fi
+ printf "$SOURCES_PREAMBLE" > "$SOURCELIST"
+ printf "$DISABLE$REPOCONFIG\n" >> "$SOURCELIST"
+ if [ $? -eq 0 ]; then
+ return 0
+ fi
+ return 2
+}
+
+# Add the Google repository to the apt sources.
+# Returns:
+# 0 - sources list was created
+# 2 - error
+create_sources_lists() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+
+ SOURCELIST="$APT_SOURCESDIR/@@PACKAGE@@.list"
+ if [ -d "$APT_SOURCESDIR" ]; then
+ printf "$SOURCES_PREAMBLE" > "$SOURCELIST"
+ printf "$REPOCONFIG\n" >> "$SOURCELIST"
+ if [ $? -eq 0 ]; then
+ return 0
+ fi
+ fi
+ return 2
+}
+
+# Remove our custom sources list file.
+# Returns:
+# 0 - successfully removed, or not configured
+# !0 - failed to remove
+clean_sources_lists() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+
+ rm -f "$APT_SOURCESDIR/@@PACKAGE@@.list" \
+ "$APT_SOURCESDIR/@@PACKAGE@@-@@CHANNEL@@.list"
+}
+
+# Detect if the repo config was disabled by distro upgrade and enable if
+# necessary.
+handle_distro_upgrade() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ find_apt_sources
+ SOURCELIST="$APT_SOURCESDIR/@@PACKAGE@@.list"
+ if [ -r "$SOURCELIST" ]; then
+ REPOLINE=$(grep -E "^[[:space:]]*#[[:space:]]*$REPOCONFIG[[:space:]]*# disabled on upgrade to .*" "$SOURCELIST")
+ if [ $? -eq 0 ]; then
+ sed -i -e "s,^[[:space:]]*#[[:space:]]*\($REPOCONFIG\)[[:space:]]*# disabled on upgrade to .*,\1," \
+ "$SOURCELIST"
+ LOGGER=$(which logger 2> /dev/null)
+ if [ "$LOGGER" ]; then
+ "$LOGGER" -t "$0" "Reverted repository modification: $REPOLINE."
+ fi
+ fi
+ fi
+}
+
--- /dev/null
+# Recursively replace @@include@@ template variables with the referenced file,
+# and write the resulting text to stdout.
+process_template_includes() {
+ INCSTACK+="$1->"
+ # Includes are relative to the file that does the include.
+ INCDIR=$(dirname $1)
+ # Clear IFS so 'read' doesn't trim whitespace
+ local OLDIFS="$IFS"
+ IFS=''
+ while read -r LINE
+ do
+ INCLINE=$(sed -e '/^[[:space:]]*@@include@@/!d' <<<$LINE)
+ if [ -n "$INCLINE" ]; then
+ INCFILE=$(echo $INCLINE | sed -e "s#@@include@@\(.*\)#\1#")
+ # Simple filename match to detect cyclic includes.
+ CYCLE=$(sed -e "\#$INCFILE#"'!d' <<<$INCSTACK)
+ if [ "$CYCLE" ]; then
+ echo "ERROR: Possible cyclic include detected." 1>&2
+ echo "$INCSTACK$INCFILE" 1>&2
+ exit 1
+ fi
+ if [ ! -r "$INCDIR/$INCFILE" ]; then
+ echo "ERROR: Couldn't read include file: $INCDIR/$INCFILE" 1>&2
+ exit 1
+ fi
+ process_template_includes "$INCDIR/$INCFILE"
+ else
+ echo "$LINE"
+ fi
+ done < "$1"
+ IFS="$OLDIFS"
+ INCSTACK=${INCSTACK%"$1->"}
+}
+
+# Replace template variables (@@VARNAME@@) in the given template file. If a
+# second argument is given, save the processed text to that filename, otherwise
+# modify the template file in place.
+process_template() (
+ # Don't worry if some of these substitution variables aren't set.
+ # Note that this function is run in a sub-shell so we don't leak this
+ # setting, since we still want unbound variables to be an error elsewhere.
+ set +u
+
+ local TMPLIN="$1"
+ if [ -z "$2" ]; then
+ local TMPLOUT="$TMPLIN"
+ else
+ local TMPLOUT="$2"
+ fi
+ # Process includes first so included text also gets substitutions.
+ TMPLINCL="$(process_template_includes "$TMPLIN")"
+ sed \
+ -e "s#@@PACKAGE@@#${PACKAGE}#g" \
+ -e "s#@@CHANNEL@@#${CHANNEL}#g" \
+ -e "s#@@COMPANY_FULLNAME@@#${COMPANY_FULLNAME}#g" \
+ -e "s#@@VERSION@@#${VERSION}#g" \
+ -e "s#@@REVISION@@#${REVISION}#g" \
+ -e "s#@@VERSIONFULL@@#${VERSIONFULL}#g" \
+ -e "s#@@BUILDDIR@@#${BUILDDIR}#g" \
+ -e "s#@@STAGEDIR@@#${STAGEDIR}#g" \
+ -e "s#@@SCRIPTDIR@@#${SCRIPTDIR}#g" \
+ -e "s#@@PRODUCTURL@@#${PRODUCTURL}#g" \
+ -e "s#@@PREDEPENDS@@#${PREDEPENDS}#g" \
+ -e "s#@@DEPENDS@@#${DEPENDS}#g" \
+ -e "s#@@PROVIDES@@#${PROVIDES}#g" \
+ -e "s#@@REPLACES@@#${REPLACES}#g" \
+ -e "s#@@CONFLICTS@@#${CONFLICTS}#g" \
+ -e "s#@@ARCHITECTURE@@#${HOST_ARCH}#g" \
+ -e "s#@@MAINTNAME@@#${MAINTNAME}#g" \
+ -e "s#@@MAINTMAIL@@#${MAINTMAIL}#g" \
+ -e "s#@@REPOCONFIG@@#${REPOCONFIG}#g" \
+ -e "s#@@SHORTDESC@@#${SHORTDESC}#g" \
+ -e "s#@@FULLDESC@@#${FULLDESC}#g" \
+ -e "s#@@APACHE_CONFDIR@@#${APACHE_CONFDIR}#g" \
+ -e "s#@@APACHE_MODULEDIR@@#${APACHE_MODULEDIR}#g" \
+ -e "s#@@APACHE_USER@@#${APACHE_USER}#g" \
+ -e "s#@@MODSPDY_ENABLE_UPDATES@@#${MODSPDY_ENABLE_UPDATES}#g" \
+ -e "s#@@COMMENT_OUT_DEFLATE@@#${COMMENT_OUT_DEFLATE}#g" \
+ > "$TMPLOUT" <<< "$TMPLINCL"
+)
+
+# Setup the installation directory hierachy in the package staging area.
+prep_staging_common() {
+ install -m 755 -d \
+ "${STAGEDIR}${APACHE_CONFDIR}" \
+ "${STAGEDIR}${APACHE_MODULEDIR}"
+}
+
+get_version_info() {
+ # Default to a bogus low version, so if somebody creates and installs
+ # a package with no version info, it won't prevent upgrading when
+ # trying to install a properly versioned package (i.e. a proper
+ # package will always be "newer").
+ VERSION="0.0.0.0"
+ # Use epoch timestamp so packages with bogus versions still increment
+ # and will upgrade older bogus-versioned packages.
+ REVISION=$(date +"%s")
+ # Default to non-official build since official builds set this
+ # properly.
+ OFFICIAL_BUILD=0
+
+ VERSIONFILE="${BUILDDIR}/installer/version.txt"
+ if [ -f "${VERSIONFILE}" ]; then
+ source "${VERSIONFILE}"
+ VERSION="${MAJOR}.${MINOR}.${BUILD}.${PATCH}"
+ REVISION="${LASTCHANGE}"
+ fi
+}
+
+stage_install_common() {
+ echo "Staging common install files in '${STAGEDIR}'..."
+
+ # app and resources
+ install -m 644 -s "${BUILDDIR}/libmod_spdy.so" "${STAGEDIR}${APACHE_MODULEDIR}/mod_spdy.so"
+ install -m 644 -s "${BUILDDIR}/mod_ssl.so" "${STAGEDIR}${APACHE_MODULEDIR}/mod_ssl_with_npn.so"
+}
--- /dev/null
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This file provides common configuration information for building
+# mod-spdy packages for various platforms.
+
+# Base name of the package.
+PACKAGE="mod-spdy"
+
+# Brief package description.
+SHORTDESC="Apache 2 module to enable SPDY support."
+
+# Detailed package description.
+FULLDESC="mod_spdy is an Apache module that allows an Apache server to support the SPDY protocol for serving HTTP resources."
+
+# Package maintainer information.
+MAINTNAME="mod_spdy developers"
+MAINTMAIL="mod-spdy-dev@googlegroups.com"
+PRODUCTURL="http://code.google.com/p/mod-spdy/"
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This script is part of the @@PACKAGE@@ package.
+#
+# It creates the repository configuration file for package updates, and it
+# monitors that config to see if it has been disabled by the overly aggressive
+# distro upgrade process (e.g. intrepid -> jaunty). When this situation is
+# detected, the respository will be re-enabled. If the respository is disabled
+# for any other reason, this won't re-enable it.
+#
+# This functionality can be controlled by creating the $DEFAULTS_FILE and
+# setting "repo_add_once" and/or "repo_reenable_on_distupgrade" to "true" or
+# "false" as desired. An empty $DEFAULTS_FILE is the same as setting both values
+# to "false".
+
+@@include@@apt.include
+
+## MAIN ##
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+if [ -r "$DEFAULTS_FILE" ]; then
+ . "$DEFAULTS_FILE"
+fi
+
+if [ "$repo_add_once" = "true" ]; then
+ install_key
+ create_sources_lists
+ RES=$?
+ # Sources creation succeeded, so stop trying.
+ if [ $RES -ne 2 ]; then
+ sed -i -e 's/[[:space:]]*repo_add_once=.*/repo_add_once="false"/' "$DEFAULTS_FILE"
+ fi
+else
+ update_bad_sources
+fi
+
+if [ "$repo_reenable_on_distupgrade" = "true" ]; then
+ handle_distro_upgrade
+fi
--- /dev/null
+@@include@@variables.include
+
+# Install the repository signing key (see also:
+# http://www.google.com/linuxrepositories/aboutkey.html)
+install_rpm_key() {
+ # Check to see if key already exists.
+ rpm -q gpg-pubkey-7fac5991-4615767f > /dev/null 2>&1
+ if [ "$?" -eq "0" ]; then
+ # Key already exists
+ return 0
+ fi
+ # This is to work around a bug in RPM 4.7.0. (see http://crbug.com/22312)
+ rpm -q gpg-pubkey-7fac5991-45f06f46 > /dev/null 2>&1
+ if [ "$?" -eq "0" ]; then
+ # Key already exists
+ return 0
+ fi
+
+ # RPM on Mandriva 2009 is dumb and does not understand "rpm --import -"
+ TMPKEY=$(mktemp /tmp/google.sig.XXXXXX)
+ if [ -n "$TMPKEY" ]; then
+ cat > "$TMPKEY" <<KEYDATA
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.2.2 (GNU/Linux)
+
+mQGiBEXwb0YRBADQva2NLpYXxgjNkbuP0LnPoEXruGmvi3XMIxjEUFuGNCP4Rj/a
+kv2E5VixBP1vcQFDRJ+p1puh8NU0XERlhpyZrVMzzS/RdWdyXf7E5S8oqNXsoD1z
+fvmI+i9b2EhHAA19Kgw7ifV8vMa4tkwslEmcTiwiw8lyUl28Wh4Et8SxzwCggDcA
+feGqtn3PP5YAdD0km4S4XeMEAJjlrqPoPv2Gf//tfznY2UyS9PUqFCPLHgFLe80u
+QhI2U5jt6jUKN4fHauvR6z3seSAsh1YyzyZCKxJFEKXCCqnrFSoh4WSJsbFNc4PN
+b0V0SqiTCkWADZyLT5wll8sWuQ5ylTf3z1ENoHf+G3um3/wk/+xmEHvj9HCTBEXP
+78X0A/0Tqlhc2RBnEf+AqxWvM8sk8LzJI/XGjwBvKfXe+l3rnSR2kEAvGzj5Sg0X
+4XmfTg4Jl8BNjWyvm2Wmjfet41LPmYJKsux3g0b8yzQxeOA4pQKKAU3Z4+rgzGmf
+HdwCG5MNT2A5XxD/eDd+L4fRx0HbFkIQoAi1J3YWQSiTk15fw7RMR29vZ2xlLCBJ
+bmMuIExpbnV4IFBhY2thZ2UgU2lnbmluZyBLZXkgPGxpbnV4LXBhY2thZ2VzLWtl
+eW1hc3RlckBnb29nbGUuY29tPohjBBMRAgAjAhsDBgsJCAcDAgQVAggDBBYCAwEC
+HgECF4AFAkYVdn8CGQEACgkQoECDD3+sWZHKSgCfdq3HtNYJLv+XZleb6HN4zOcF
+AJEAniSFbuv8V5FSHxeRimHx25671az+uQINBEXwb0sQCACuA8HT2nr+FM5y/kzI
+A51ZcC46KFtIDgjQJ31Q3OrkYP8LbxOpKMRIzvOZrsjOlFmDVqitiVc7qj3lYp6U
+rgNVaFv6Qu4bo2/ctjNHDDBdv6nufmusJUWq/9TwieepM/cwnXd+HMxu1XBKRVk9
+XyAZ9SvfcW4EtxVgysI+XlptKFa5JCqFM3qJllVohMmr7lMwO8+sxTWTXqxsptJo
+pZeKz+UBEEqPyw7CUIVYGC9ENEtIMFvAvPqnhj1GS96REMpry+5s9WKuLEaclWpd
+K3krttbDlY1NaeQUCRvBYZ8iAG9YSLHUHMTuI2oea07Rh4dtIAqPwAX8xn36JAYG
+2vgLAAMFB/wKqaycjWAZwIe98Yt0qHsdkpmIbarD9fGiA6kfkK/UxjL/k7tmS4Vm
+CljrrDZkPSQ/19mpdRcGXtb0NI9+nyM5trweTvtPw+HPkDiJlTaiCcx+izg79Fj9
+KcofuNb3lPdXZb9tzf5oDnmm/B+4vkeTuEZJ//IFty8cmvCpzvY+DAz1Vo9rA+Zn
+cpWY1n6z6oSS9AsyT/IFlWWBZZ17SpMHu+h4Bxy62+AbPHKGSujEGQhWq8ZRoJAT
+G0KSObnmZ7FwFWu1e9XFoUCt0bSjiJWTIyaObMrWu/LvJ3e9I87HseSJStfw6fki
+5og9qFEkMrIrBCp3QGuQWBq/rTdMuwNFiEkEGBECAAkFAkXwb0sCGwwACgkQoECD
+D3+sWZF/WACfeNAu1/1hwZtUo1bR+MWiCjpvHtwAnA1R3IHqFLQ2X3xJ40XPuAyY
+/FJG
+=Quqp
+-----END PGP PUBLIC KEY BLOCK-----
+KEYDATA
+ rpm --import "$TMPKEY"
+ rc=$?
+ rm -f "$TMPKEY"
+ if [ "$rc" -eq "0" ]; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+determine_rpm_package_manager() {
+ local RELEASE
+ LSB_RELEASE="$(which lsb_release 2> /dev/null)"
+ if [ -x "$LSB_RELEASE" ]; then
+ RELEASE=$(lsb_release -i 2> /dev/null)
+ case $DISTRIB_ID in
+ "Fedora")
+ PACKAGEMANAGER=yum
+ ;;
+ "MandrivaLinux")
+ PACKAGEMANAGER=urpmi
+ ;;
+ "SUSE LINUX")
+ PACKAGEMANAGER=yast
+ ;;
+ esac
+ fi
+
+ if [ "$PACKAGEMANAGER" ]; then
+ return
+ fi
+
+ # Fallback methods that are probably unnecessary on modern systems.
+ if [ -f "/etc/lsb-release" ]; then
+ # file missing on Fedora, does not contain DISTRIB_ID on OpenSUSE.
+ eval $(sed -e '/DISTRIB_ID/!d' /etc/lsb-release)
+ case $DISTRIB_ID in
+ MandrivaLinux)
+ PACKAGEMANAGER=urpmi
+ ;;
+ esac
+ fi
+
+ if [ "$PACKAGEMANAGER" ]; then
+ return
+ fi
+
+ if [ -f "/etc/fedora-release" ] || [ -f "/etc/redhat-release" ]; then
+ PACKAGEMANAGER=yum
+ elif [ -f "/etc/SuSE-release" ]; then
+ PACKAGEMANAGER=yast
+ elif [ -f "/etc/mandriva-release" ]; then
+ PACKAGEMANAGER=urpmi
+ fi
+}
+
+DEFAULT_ARCH="@@ARCHITECTURE@@"
+YUM_REPO_FILE="/etc/yum.repos.d/@@PACKAGE@@.repo"
+ZYPPER_REPO_FILE="/etc/zypp/repos.d/@@PACKAGE@@.repo"
+URPMI_REPO_FILE="/etc/urpmi/urpmi.cfg"
+
+install_yum() {
+ install_rpm_key
+
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ if [ -d "/etc/yum.repos.d" ]; then
+cat > "$YUM_REPO_FILE" << REPOCONTENT
+[@@PACKAGE@@]
+name=@@PACKAGE@@
+baseurl=$REPOCONFIG/$DEFAULT_ARCH
+enabled=1
+gpgcheck=1
+REPOCONTENT
+ fi
+}
+
+# This is called by the cron job, rather than in the RPM postinstall.
+# We cannot do this during the install when urpmi is running due to
+# database locking. We also need to enable the repository, and we can
+# only do that while we are online.
+# see: https://qa.mandriva.com/show_bug.cgi?id=31893
+configure_urpmi() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ urpmq --list-media | grep -q -s "^@@PACKAGE@@$"
+ if [ "$?" -eq "0" ]; then
+ # Repository already configured
+ return 0
+ fi
+ urpmi.addmedia --update \
+ "@@PACKAGE@@" "$REPOCONFIG/$DEFAULT_ARCH"
+}
+
+install_urpmi() {
+ # urpmi not smart enough to pull media_info/pubkey from the repository?
+ install_rpm_key
+
+ # Defer urpmi.addmedia to configure_urpmi() in the cron job.
+ # See comment there.
+ #
+ # urpmi.addmedia --update \
+ # "@@PACKAGE@@" "$REPOCONFIG/$DEFAULT_ARCH"
+}
+
+install_yast() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ # We defer adding the key to later. See comment in the cron job.
+
+ # Ideally, we would run: zypper addrepo -t YUM -f \
+ # "$REPOCONFIG/$DEFAULT_ARCH" "@@PACKAGE@@"
+ # but that does not work when zypper is running.
+ if [ -d "/etc/zypp/repos.d" ]; then
+cat > "$ZYPPER_REPO_FILE" << REPOCONTENT
+[@@PACKAGE@@]
+name=@@PACKAGE@@
+enabled=1
+autorefresh=1
+baseurl=$REPOCONFIG/$DEFAULT_ARCH
+type=rpm-md
+keeppackages=0
+REPOCONTENT
+ fi
+}
+
+# Check if the automatic repository configuration is done, so we know when to
+# stop trying.
+verify_install() {
+ # It's probably enough to see that the repo configs have been created. If they
+ # aren't configured properly, update_bad_repo should catch that when it's run.
+ case $1 in
+ "yum")
+ [ -f "$YUM_REPO_FILE" ]
+ ;;
+ "yast")
+ [ -f "$ZYPPER_REPO_FILE" ]
+ ;;
+ "urpmi")
+ urpmq --list-url | grep -q -s "\b@@PACKAGE@@\b"
+ ;;
+ esac
+}
+
+# Update the Google repository if it's not set correctly.
+update_bad_repo() {
+ if [ ! "$REPOCONFIG" ]; then
+ return 0
+ fi
+
+ determine_rpm_package_manager
+
+ case $PACKAGEMANAGER in
+ "yum")
+ update_repo_file "$YUM_REPO_FILE"
+ ;;
+ "yast")
+ update_repo_file "$ZYPPER_REPO_FILE"
+ ;;
+ "urpmi")
+ update_urpmi_cfg
+ ;;
+ esac
+}
+
+update_repo_file() {
+ REPO_FILE="$1"
+
+ # Don't do anything if the file isn't there, since that probably means the
+ # user disabled it.
+ if [ ! -r "$REPO_FILE" ]; then
+ return 0
+ fi
+
+ # Check if the correct repository configuration is in there.
+ REPOMATCH=$(grep "^baseurl=$REPOCONFIG/$DEFAULT_ARCH" "$REPO_FILE" \
+ 2>/dev/null)
+ # If it's there, nothing to do
+ if [ "$REPOMATCH" ]; then
+ return 0
+ fi
+
+ # Check if it's there but disabled by commenting out (as opposed to using the
+ # 'enabled' setting).
+ MATCH_DISABLED=$(grep "^[[:space:]]*#.*baseurl=$REPOCONFIG/$DEFAULT_ARCH" \
+ "$REPO_FILE" 2>/dev/null)
+ if [ "$MATCH_DISABLED" ]; then
+ # It's OK for it to be disabled, as long as nothing bogus is enabled in its
+ # place.
+ ACTIVECONFIGS=$(grep "^baseurl=.*" "$REPO_FILE" 2>/dev/null)
+ if [ ! "$ACTIVECONFIGS" ]; then
+ return 0
+ fi
+ fi
+
+ # If we get here, the correct repository wasn't found, or something else is
+ # active, so fix it. This assumes there is a 'baseurl' setting, but if not,
+ # then that's just another way of disabling, so we won't try to add it.
+ sed -i -e "s,^baseurl=.*,baseurl=$REPOCONFIG/$DEFAULT_ARCH," "$REPO_FILE"
+}
+
+update_urpmi_cfg() {
+ REPOCFG=$(urpmq --list-url | grep "\b@@PACKAGE@@\b")
+ if [ ! "$REPOCFG" ]; then
+ # Don't do anything if the repo isn't there, since that probably means the
+ # user deleted it.
+ return 0
+ fi
+
+ # See if it's the right repo URL
+ REPOMATCH=$(echo "$REPOCFG" | grep "\b$REPOCONFIG/$DEFAULT_ARCH\b")
+ # If so, nothing to do
+ if [ "$REPOMATCH" ]; then
+ return 0
+ fi
+
+ # Looks like it's the wrong URL, so recreate it.
+ urpmi.removemedia "@@PACKAGE@@" && \
+ urpmi.addmedia --update "@@PACKAGE@@" "$REPOCONFIG/$DEFAULT_ARCH"
+}
+
+# We only remove the repository configuration during a purge. Since RPM has
+# no equivalent to dpkg --purge, the code below is actually never used. We
+# keep it only for reference purposes, should we ever need it.
+#
+#remove_yum() {
+# rm -f "$YUM_REPO_FILE"
+#}
+#
+#remove_urpmi() {
+# # Ideally, we would run: urpmi.removemedia "@@PACKAGE@@"
+# # but that does not work when urpmi is running.
+# # Sentinel comment text does not work either because urpmi.update removes
+# # all comments. So we just delete the entry that matches what we originally
+# # inserted. If such an entry was added manually, that's tough luck.
+# if [ -f "$URPMI_REPO_FILE" ]; then
+# sed -i '\_^@@PACKAGE@@ $REPOCONFIG/$DEFAULT_ARCH {$_,/^}$/d' "$URPMI_REPO_FILE"
+# fi
+#}
+#
+#remove_yast() {
+# # Ideally, we would run: zypper removerepo "@@PACKAGE@@"
+# # but that does not work when zypper is running.
+# rm -f /etc/zypp/repos.d/@@PACKAGE@@.repo
+#}
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This script is part of the @@PACKAGE@@ package.
+#
+# It creates the repository configuration file for package updates, since
+# we cannot do this during the @@PACKAGE@@ installation since the repository
+# is locked.
+#
+# This functionality can be controlled by creating the $DEFAULTS_FILE and
+# setting "repo_add_once" to "true" or "false" as desired. An empty
+# $DEFAULTS_FILE is the same as setting the value to "false".
+
+@@include@@rpm.include
+
+## MAIN ##
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+if [ -r "$DEFAULTS_FILE" ]; then
+ . "$DEFAULTS_FILE"
+fi
+
+if [ "$repo_add_once" = "true" ]; then
+ determine_rpm_package_manager
+
+ case $PACKAGEMANAGER in
+ "urpmi")
+ # We need to configure urpmi after the install has finished.
+ # See configure_urpmi() for details.
+ configure_urpmi
+ ;;
+ "yast")
+ # It looks as though yast/zypper has a lock on the RPM DB during
+ # postinstall, so we cannot add the signing key with install_rpm_key().
+ # Instead, we attempt to do this here. If the user attempt to update before
+ # the cron job imports the key, Yast will grab the key from our server and
+ # prompt the user to accept the key.
+ install_rpm_key
+ ;;
+ esac
+
+ if [ $? -eq 0 ]; then
+ # Before we quit auto-configuration, check that everything looks sane, since
+ # part of this happened during package install and we don't have the return
+ # value of that process.
+ verify_install $PACKAGEMANAGER
+ if [ $? -eq 0 ]; then
+ sed -i -e 's/[[:space:]]*repo_add_once=.*/repo_add_once="false"/' \
+ "$DEFAULTS_FILE"
+ fi
+ fi
+else
+ update_bad_repo
+fi
--- /dev/null
+<IfModule spdy_module>
+ # Turn on mod_spdy. To completely disable mod_spdy, you can set
+ # this to "off".
+ SpdyEnabled on
+
+ # In order to support concurrent multiplexing of requests over a
+ # single connection, mod_spdy maintains its own thread pool in
+ # each Apache child process for processing requests. The default
+ # size of this thread pool is very conservative; you can override
+ # it with a larger value (as below) to increase concurrency, at
+ # the possible cost of increased memory usage.
+ #
+ #SpdyMaxThreadsPerProcess 30
+
+ # Memory usage can also be affected by the maximum number of
+ # simultaneously open SPDY streams permitted for each client
+ # connection. Ideally, this limit should be set as high as
+ # possible, but you can tweak it as necessary to limit memory
+ # consumption.
+ #
+ #SpdyMaxStreamsPerConnection 100
+</IfModule>
--- /dev/null
+LoadModule spdy_module @@APACHE_MODULEDIR@@/mod_spdy.so
--- /dev/null
+# This version of ssl.load was placed here because you installed mod_spdy.
+
+# Using mod_spdy requires using a patched version of mod_ssl that provides
+# hooks into the Next Protocol Negotiation (NPN) data from the SSL handshake.
+# Thus, the mod_spdy package installs mod_ssl_with_npn.so, which is exactly
+# mod_ssl but with the following (small) patch applied:
+# https://issues.apache.org/bugzilla/attachment.cgi?id=27969
+
+LoadModule ssl_module @@APACHE_MODULEDIR@@/mod_ssl_with_npn.so
+
+# If you'd like to go back to using the original, unpatched version of mod_ssl,
+# simply comment out the above line and uncomment the below line. However,
+# beware that mod_spdy will probably then cease to function.
+
+#LoadModule ssl_module @@APACHE_MODULEDIR@@/mod_ssl.so
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO
+# - handle other distros (e.g. non-apt).
+
+@@include@@apt.include
+
+if [ -x "$APT_GET" ]; then
+ update_sources_lists
+ # If the repo was just added, force a cache update.
+ if [ $? -eq 1 ]; then
+ install_key
+ "$APT_GET" -qq update
+ fi
+
+ # TODO(mmoss) detect if apt cache is stale (> 1 day) and force update?
+
+ # Just try to install the packge. If it's already installed, apt-get won't do
+ # anything.
+ "$APT_GET" install -y -q @@PACKAGE@@
+fi
+
--- /dev/null
+# System-wide package configuration.
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+
+# sources.list setting for @@PACKAGE@@ updates.
+REPOCONFIG="@@REPOCONFIG@@"
--- /dev/null
+#!/bin/bash
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+if [ "$VERBOSE" ]; then
+ set -x
+fi
+set -u
+
+# Create the Debian changelog file needed by dpkg-gencontrol. This just adds a
+# placeholder change, indicating it is the result of an automatic build.
+gen_changelog() {
+ rm -f "${DEB_CHANGELOG}"
+ process_template "${SCRIPTDIR}/changelog.template" "${DEB_CHANGELOG}"
+ debchange -a --nomultimaint -m --changelog "${DEB_CHANGELOG}" \
+ --distribution UNRELEASED "automatic build"
+}
+
+# Create the Debian control file needed by dpkg-deb.
+gen_control() {
+ dpkg-gencontrol -v"${VERSIONFULL}" -c"${DEB_CONTROL}" -l"${DEB_CHANGELOG}" \
+ -f"${DEB_FILES}" -p"${PACKAGE}-${CHANNEL}" -P"${STAGEDIR}" -T"${DEB_SUBST}" \
+ -O > "${STAGEDIR}/DEBIAN/control"
+ rm -f "${DEB_CONTROL}"
+}
+
+# Create the Debian substvars file needed by dpkg-gencontrol.
+gen_substvars() {
+ # dpkg-shlibdeps requires a control file in debian/control, so we're
+ # forced to prepare a fake debian directory.
+ mkdir "${SUBSTFILEDIR}/debian"
+ cp "${DEB_CONTROL}" "${SUBSTFILEDIR}/debian"
+ pushd "${SUBSTFILEDIR}" >/dev/null
+ dpkg-shlibdeps "${STAGEDIR}${APACHE_MODULEDIR}/mod_spdy.so" \
+ -O >> "${DEB_SUBST}" 2>/dev/null
+ popd >/dev/null
+}
+
+# Setup the installation directory hierachy in the package staging area.
+prep_staging_debian() {
+ prep_staging_common
+ install -m 755 -d "${STAGEDIR}/DEBIAN" \
+ "${STAGEDIR}/etc/cron.daily"
+}
+
+# Put the package contents in the staging area.
+stage_install_debian() {
+ prep_staging_debian
+ stage_install_common
+ echo "Staging Debian install files in '${STAGEDIR}'..."
+ process_template "${BUILDDIR}/install/common/repo.cron" \
+ "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+ chmod 755 "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+ process_template "${BUILDDIR}/install/debian/preinst" \
+ "${STAGEDIR}/DEBIAN/preinst"
+ chmod 755 "${STAGEDIR}/DEBIAN/preinst"
+ process_template "${BUILDDIR}/install/debian/postinst" \
+ "${STAGEDIR}/DEBIAN/postinst"
+ chmod 755 "${STAGEDIR}/DEBIAN/postinst"
+ process_template "${BUILDDIR}/install/debian/prerm" \
+ "${STAGEDIR}/DEBIAN/prerm"
+ chmod 755 "${STAGEDIR}/DEBIAN/prerm"
+ process_template "${BUILDDIR}/install/debian/postrm" \
+ "${STAGEDIR}/DEBIAN/postrm"
+ chmod 755 "${STAGEDIR}/DEBIAN/postrm"
+ process_template "${BUILDDIR}/install/debian/conffiles.template" \
+ "${STAGEDIR}/DEBIAN/conffiles"
+ chmod 644 "${STAGEDIR}/DEBIAN/conffiles"
+ process_template "${BUILDDIR}/install/common/spdy.load.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/spdy.load"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/spdy.load"
+ process_template "${BUILDDIR}/install/common/spdy.conf.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/spdy.conf"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/spdy.conf"
+}
+
+# Build the deb file within a fakeroot.
+do_package_in_fakeroot() {
+ FAKEROOTFILE=$(mktemp -t fakeroot.tmp.XXXXXX) || exit 1
+ fakeroot -i "${FAKEROOTFILE}" -- \
+ dpkg-deb -b "${STAGEDIR}" .
+ rm -f "${FAKEROOTFILE}"
+}
+
+# Actually generate the package file.
+do_package() {
+ export HOST_ARCH="$1"
+ echo "Packaging ${HOST_ARCH}..."
+ PREDEPENDS="$COMMON_PREDEPS"
+ DEPENDS="${COMMON_DEPS}"
+ gen_changelog
+ process_template "${SCRIPTDIR}/control.template" "${DEB_CONTROL}"
+ export DEB_HOST_ARCH="${HOST_ARCH}"
+ gen_substvars
+ if [ -f "${DEB_CONTROL}" ]; then
+ gen_control
+ fi
+
+ do_package_in_fakeroot
+}
+
+# Remove temporary files and unwanted packaging output.
+cleanup() {
+ echo "Cleaning..."
+ rm -rf "${STAGEDIR}"
+ rm -rf "${TMPFILEDIR}"
+ rm -rf "${SUBSTFILEDIR}"
+}
+
+usage() {
+ echo "usage: $(basename $0) [-c channel] [-a target_arch] [-o 'dir'] [-b 'dir']"
+ echo "-c channel the package channel (unstable, beta, stable)"
+ echo "-a arch package architecture (ia32 or x64)"
+ echo "-o dir package output directory [${OUTPUTDIR}]"
+ echo "-b dir build input directory [${BUILDDIR}]"
+ echo "-h this help message"
+}
+
+# Check that the channel name is one of the allowable ones.
+verify_channel() {
+ case $CHANNEL in
+ stable )
+ CHANNEL=stable
+ ;;
+ unstable|dev|alpha )
+ CHANNEL=unstable
+ ;;
+ testing|beta )
+ CHANNEL=beta
+ ;;
+ * )
+ echo
+ echo "ERROR: '$CHANNEL' is not a valid channel type."
+ echo
+ exit 1
+ ;;
+ esac
+}
+
+process_opts() {
+ while getopts ":o:b:c:a:h" OPTNAME
+ do
+ case $OPTNAME in
+ o )
+ OUTPUTDIR="$OPTARG"
+ mkdir -p "${OUTPUTDIR}"
+ ;;
+ b )
+ BUILDDIR=$(readlink -f "${OPTARG}")
+ ;;
+ c )
+ CHANNEL="$OPTARG"
+ ;;
+ a )
+ TARGETARCH="$OPTARG"
+ ;;
+ h )
+ usage
+ exit 0
+ ;;
+ \: )
+ echo "'-$OPTARG' needs an argument."
+ usage
+ exit 1
+ ;;
+ * )
+ echo "invalid command-line option: $OPTARG"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+}
+
+#=========
+# MAIN
+#=========
+
+SCRIPTDIR=$(readlink -f "$(dirname "$0")")
+OUTPUTDIR="${PWD}"
+STAGEDIR=$(mktemp -d -t deb.build.XXXXXX) || exit 1
+TMPFILEDIR=$(mktemp -d -t deb.tmp.XXXXXX) || exit 1
+SUBSTFILEDIR=$(mktemp -d -t deb.subst.XXXXXX) || exit 1
+DEB_CHANGELOG="${TMPFILEDIR}/changelog"
+DEB_FILES="${TMPFILEDIR}/files"
+DEB_CONTROL="${TMPFILEDIR}/control"
+DEB_SUBST="${SUBSTFILEDIR}/debian/substvars"
+CHANNEL="beta"
+# Default target architecture to same as build host.
+if [ "$(uname -m)" = "x86_64" ]; then
+ TARGETARCH="x64"
+else
+ TARGETARCH="ia32"
+fi
+
+# call cleanup() on exit
+trap cleanup 0
+process_opts "$@"
+if [ ! "$BUILDDIR" ]; then
+ BUILDDIR=$(readlink -f "${BUILDDIR}/install/../mod-spdy-release/src/out/Release")
+fi
+
+source ${BUILDDIR}/install/common/installer.include
+
+get_version_info
+VERSIONFULL="${VERSION}-r${REVISION}"
+
+source "${BUILDDIR}/install/common/mod-spdy.info"
+eval $(sed -e "s/^\([^=]\+\)=\(.*\)$/export \1='\2'/" \
+ "${BUILDDIR}/install/common/BRANDING")
+
+REPOCONFIG="deb http://dl.google.com/linux/${PACKAGE#google-}/deb/ stable main"
+verify_channel
+
+# Some Debian packaging tools want these set.
+export DEBFULLNAME="${MAINTNAME}"
+export DEBEMAIL="${MAINTMAIL}"
+
+# Make everything happen in the OUTPUTDIR.
+cd "${OUTPUTDIR}"
+
+COMMON_DEPS="apache2.2-common"
+COMMON_PREDEPS="dpkg (>= 1.14.0)"
+REPLACES=""
+
+APACHE_MODULEDIR="/usr/lib/apache2/modules"
+APACHE_CONFDIR="/etc/apache2/mods-available"
+APACHE_USER="www-data"
+COMMENT_OUT_DEFLATE=
+
+case "$TARGETARCH" in
+ ia32 )
+ stage_install_debian
+ do_package "i386"
+ ;;
+ x64 )
+ stage_install_debian
+ do_package "amd64"
+ ;;
+ * )
+ echo
+ echo "ERROR: Don't know how to build DEBs for '$TARGETARCH'."
+ echo
+ exit 1
+ ;;
+esac
--- /dev/null
+@@PACKAGE@@-@@CHANNEL@@ (@@VERSIONFULL@@) UNRELEASED; urgency=low
+ * No changes
+
+ -- @@MAINTNAME@@ <@@MAINTMAIL@@> Wed, 20 Oct 2010 14:54:35 -0800
--- /dev/null
+/etc/apache2/mods-available/spdy.load
+/etc/apache2/mods-available/spdy.conf
+/etc/cron.daily/@@PACKAGE@@
--- /dev/null
+Source: @@PACKAGE@@-@@CHANNEL@@
+Section: httpd
+Priority: optional
+Maintainer: @@MAINTNAME@@ <@@MAINTMAIL@@>
+Build-Depends: dpkg-dev, devscripts, fakeroot
+Standards-Version: 3.8.0
+
+Package: @@PACKAGE@@-@@CHANNEL@@
+Provides: @@PROVIDES@@
+Replaces: @@REPLACES@@
+Conflicts: @@CONFLICTS@@
+Pre-Depends: @@PREDEPENDS@@
+Depends: ${shlibs:Depends}, @@DEPENDS@@
+Architecture: @@ARCHITECTURE@@
+Description: @@SHORTDESC@@
+ @@FULLDESC@@
--- /dev/null
+#!/bin/sh
+
+# Based on postinst from Chromium and Google Talk.
+
+@@include@@../common/apt.include
+
+MODSPDY_ENABLE_UPDATES=@@MODSPDY_ENABLE_UPDATES@@
+
+case "$1" in
+ configure)
+ if [ -n "${MODSPDY_ENABLE_UPDATES}" -a ! -e "$DEFAULTS_FILE" ]; then
+ echo 'repo_add_once="true"' > "$DEFAULTS_FILE"
+ echo 'repo_reenable_on_distupgrade="true"' >> "$DEFAULTS_FILE"
+ fi
+
+ # Run the cron job immediately to perform repository
+ # configuration.
+ nohup sh /etc/cron.daily/@@PACKAGE@@ > /dev/null 2>&1 &
+
+ test ! -e /etc/apache2/mods-enabled/spdy.load && \
+ a2enmod spdy
+ ;;
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+set -e # If any command fails from here on, the whole scripts fails.
+
+# Regardless of what argument postinst is called with, we should make sure at
+# this point that we're set up to load our version of mod_ssl. Note that if we
+# upgrade mod-spdy, the old package's prerm will first get called, which will
+# undo changes to ssl.load, and then we'll redo them here. This is good, in
+# case we ever need to change the way we modify ssl.load.
+if [ -f "@@APACHE_CONFDIR@@/ssl.load" ]; then
+ # Don't do anything if the magic "MOD_SPDY" marker is already present in the
+ # file; this helps ensure that this prerm script is idempotent. See
+ # http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#s-idempotency
+ # for why this is important.
+ if ! grep -q 'MOD_SPDY' @@APACHE_CONFDIR@@/ssl.load; then
+ # First, comment out all lines in the file, using a special prefix. We
+ # will look for that prefix later when we uninstall.
+ sed --in-place 's/^.*$/#ORIG# &/' @@APACHE_CONFDIR@@/ssl.load
+ # Next, append a new LoadModule line to the file, with some explanitory
+ # comments. The first line we append contains the magic marker "MOD_SPDY",
+ # which we look for in the prerm script so that we can remove the below
+ # text when we uninstall.
+ cat >> @@APACHE_CONFDIR@@/ssl.load <<EOF
+########## MOD_SPDY CHANGES BELOW ##########
+# If mod_spdy is uninstalled, this file will be restored to its original form
+# by deleting everything below here and uncommenting everything above.
+
+# Using mod_spdy requires using a patched version of mod_ssl that provides
+# hooks into the Next Protocol Negotiation (NPN) data from the SSL handshake.
+# Thus, the mod_spdy package installs mod_ssl_with_npn.so, which is exactly
+# mod_ssl but with the following (small) patch applied:
+# https://issues.apache.org/bugzilla/attachment.cgi?id=27969
+
+LoadModule ssl_module @@APACHE_MODULEDIR@@/mod_ssl_with_npn.so
+EOF
+ fi
+fi
+
+exit 0
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+action="$1"
+
+# Only do complete clean-up on purge.
+if [ "$action" != "purge" ] ; then
+ exit 0
+fi
+
+@@include@@../common/apt.include
+
+# Only remove the defaults file if it is not empty. An empty file was probably
+# put there by the sysadmin to disable automatic repository configuration, as
+# per the instructions on the package download page.
+if [ -s "$DEFAULTS_FILE" ]; then
+ # Make sure the package defaults are removed before the repository config,
+ # otherwise it could result in the repository config being removed, but the
+ # package defaults remain and are set to not recreate the repository config.
+ # In that case, future installs won't recreate it and won't get auto-updated.
+ rm "$DEFAULTS_FILE" || exit 1
+fi
+# Remove any Google repository added by the package.
+clean_sources_lists
--- /dev/null
+#!/bin/sh
+
+exit 0
--- /dev/null
+#!/bin/sh
+
+case "$1" in
+ remove)
+ test -e /etc/apache2/mods-enabled/spdy.load && a2dismod spdy
+ ;;
+ upgrade|deconfigure|failed-upgrade)
+ ;;
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+set -e # If any command fails from here on, the whole scripts fails.
+
+# Regardless of the argument prerm is called with, we should undo our changes
+# to ssl.load. If we're upgrading to a newer version of mod_spdy, the new
+# package will redo its changes to ssl.load in its postinst script. See
+# http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#s-unpackphase
+# for details.
+if [ -f "@@APACHE_CONFDIR@@/ssl.load" ]; then
+ # Don't do anything if we don't see the magic "MOD_SPDY" marker; this helps
+ # ensure that this prerm script is idempotent. See
+ # http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html#s-idempotency
+ # for why this is important.
+ if grep -q 'MOD_SPDY' @@APACHE_CONFDIR@@/ssl.load; then
+ # First, we uncomment any line that starts with "#ORIG# " (we use that
+ # particular prefix, to reduce the chances that we break the file if the
+ # user has added their own comments to the file for some reason), up until
+ # we see the "MOD_SPDY" marker. Second, we delete the line containing the
+ # "MOD_SPDY" marker and all lines thereafter, thus removing the stuff we
+ # appended to the file in the postinst script.
+ sed --in-place \
+ -e '1,/MOD_SPDY/ s/^#ORIG# \(.*\)$/\1/' \
+ -e '/MOD_SPDY/,$ d' \
+ @@APACHE_CONFDIR@@/ssl.load
+ fi
+fi
+
+exit 0
--- /dev/null
+#!/bin/bash
+#
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+if [ "$VERBOSE" ]; then
+ set -x
+fi
+set -u
+
+gen_spec() {
+ rm -f "${SPEC}"
+ process_template "${SCRIPTDIR}/mod-spdy.spec.template" "${SPEC}"
+}
+
+# Setup the installation directory hierachy in the package staging area.
+prep_staging_rpm() {
+ prep_staging_common
+ install -m 755 -d "${STAGEDIR}/etc/cron.daily"
+}
+
+# Put the package contents in the staging area.
+stage_install_rpm() {
+ prep_staging_rpm
+ stage_install_common
+ echo "Staging RPM install files in '${STAGEDIR}'..."
+ process_template "${BUILDDIR}/install/common/rpmrepo.cron" \
+ "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+ chmod 755 "${STAGEDIR}/etc/cron.daily/${PACKAGE}"
+
+ # For CentOS, the load and conf files are combined into a single
+ # 'conf' file. So we install the load template as the conf file, and
+ # then concatenate the actual conf file.
+ process_template "${BUILDDIR}/install/common/spdy.load.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/spdy.conf"
+ process_template "${BUILDDIR}/install/common/spdy.conf.template" \
+ "${BUILDDIR}/install/common/spdy.conf"
+ cat "${BUILDDIR}/install/common/spdy.conf" >> \
+ "${STAGEDIR}${APACHE_CONFDIR}/spdy.conf"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/spdy.conf"
+
+ # Our conf file for loading mod_ssl_with_npn.so must come alphabetically
+ # before the built-in ssl.conf file, so we can't call it "ssl_with_npn.conf".
+ # Since all it will do is load the module (not configure it),
+ # "load_ssl_with_npn.conf" seems like an appropriate name.
+ process_template "${BUILDDIR}/install/common/ssl.load.template" \
+ "${STAGEDIR}${APACHE_CONFDIR}/load_ssl_with_npn.conf"
+ chmod 644 "${STAGEDIR}${APACHE_CONFDIR}/load_ssl_with_npn.conf"
+}
+
+# Actually generate the package file.
+do_package() {
+ echo "Packaging ${HOST_ARCH}..."
+ PROVIDES="${PACKAGE}"
+ local REPS="$REPLACES"
+ REPLACES=""
+ for rep in $REPS; do
+ if [ -z "$REPLACES" ]; then
+ REPLACES="$PACKAGE-$rep"
+ else
+ REPLACES="$REPLACES $PACKAGE-$rep"
+ fi
+ done
+
+ # If we specify a dependecy of foo.so below, we would depend on both the
+ # 32 and 64-bit versions on a 64-bit machine. The current version of RPM
+ # we use is too old and doesn't provide %{_isa}, so we do this manually.
+ if [ "$HOST_ARCH" = "x86_64" ] ; then
+ local EMPTY_VERSION="()"
+ local PKG_ARCH="(64bit)"
+ elif [ "$HOST_ARCH" = "i386" ] ; then
+ local EMPTY_VERSION=""
+ local PKG_ARCH=""
+ fi
+
+ DEPENDS="httpd >= 2.2.4, \
+ mod_ssl >= 2.2, \
+ libstdc++ >= 4.1.2, \
+ at"
+ gen_spec
+
+ # Create temporary rpmbuild dirs.
+ RPMBUILD_DIR=$(mktemp -d -t rpmbuild.XXXXXX) || exit 1
+ mkdir -p "$RPMBUILD_DIR/BUILD"
+ mkdir -p "$RPMBUILD_DIR/RPMS"
+
+ rpmbuild --buildroot="$RPMBUILD_DIR/BUILD" -bb \
+ --target="$HOST_ARCH" --rmspec \
+ --define "_topdir $RPMBUILD_DIR" \
+ --define "_binary_payload w9.bzdio" \
+ "${SPEC}"
+ PKGNAME="${PACKAGE}-${CHANNEL}-${VERSION}-${REVISION}"
+ mv "$RPMBUILD_DIR/RPMS/$HOST_ARCH/${PKGNAME}.${HOST_ARCH}.rpm" "${OUTPUTDIR}"
+ # Make sure the package is world-readable, otherwise it causes problems when
+ # copied to share drive.
+ chmod a+r "${OUTPUTDIR}/${PKGNAME}.$HOST_ARCH.rpm"
+ rm -rf "$RPMBUILD_DIR"
+}
+
+# Remove temporary files and unwanted packaging output.
+cleanup() {
+ rm -rf "${STAGEDIR}"
+ rm -rf "${TMPFILEDIR}"
+}
+
+usage() {
+ echo "usage: $(basename $0) [-c channel] [-a target_arch] [-o 'dir'] [-b 'dir']"
+ echo "-c channel the package channel (unstable, beta, stable)"
+ echo "-a arch package architecture (ia32 or x64)"
+ echo "-o dir package output directory [${OUTPUTDIR}]"
+ echo "-b dir build input directory [${BUILDDIR}]"
+ echo "-h this help message"
+}
+
+# Check that the channel name is one of the allowable ones.
+verify_channel() {
+ case $CHANNEL in
+ stable )
+ CHANNEL=stable
+ REPLACES="unstable beta"
+ ;;
+ unstable|dev|alpha )
+ CHANNEL=unstable
+ REPLACES="stable beta"
+ ;;
+ testing|beta )
+ CHANNEL=beta
+ REPLACES="unstable stable"
+ ;;
+ * )
+ echo
+ echo "ERROR: '$CHANNEL' is not a valid channel type."
+ echo
+ exit 1
+ ;;
+ esac
+}
+
+process_opts() {
+ while getopts ":o:b:c:a:h" OPTNAME
+ do
+ case $OPTNAME in
+ o )
+ OUTPUTDIR="$OPTARG"
+ mkdir -p "${OUTPUTDIR}"
+ ;;
+ b )
+ BUILDDIR=$(readlink -f "${OPTARG}")
+ ;;
+ c )
+ CHANNEL="$OPTARG"
+ verify_channel
+ ;;
+ a )
+ TARGETARCH="$OPTARG"
+ ;;
+ h )
+ usage
+ exit 0
+ ;;
+ \: )
+ echo "'-$OPTARG' needs an argument."
+ usage
+ exit 1
+ ;;
+ * )
+ echo "invalid command-line option: $OPTARG"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+}
+
+#=========
+# MAIN
+#=========
+
+SCRIPTDIR=$(readlink -f "$(dirname "$0")")
+OUTPUTDIR="${PWD}"
+STAGEDIR=$(mktemp -d -t rpm.build.XXXXXX) || exit 1
+TMPFILEDIR=$(mktemp -d -t rpm.tmp.XXXXXX) || exit 1
+CHANNEL="beta"
+# Default target architecture to same as build host.
+if [ "$(uname -m)" = "x86_64" ]; then
+ TARGETARCH="x64"
+else
+ TARGETARCH="ia32"
+fi
+SPEC="${TMPFILEDIR}/mod-spdy.spec"
+
+# call cleanup() on exit
+trap cleanup 0
+process_opts "$@"
+if [ ! "$BUILDDIR" ]; then
+ BUILDDIR=$(readlink -f "${SCRIPTDIR}/../../out/Release")
+fi
+
+source ${BUILDDIR}/install/common/installer.include
+
+get_version_info
+
+source "${BUILDDIR}/install/common/mod-spdy.info"
+eval $(sed -e "s/^\([^=]\+\)=\(.*\)$/export \1='\2'/" \
+ "${BUILDDIR}/install/common/BRANDING")
+
+REPOCONFIG="http://dl.google.com/linux/${PACKAGE#google-}/rpm/stable"
+verify_channel
+
+APACHE_CONFDIR="/etc/httpd/conf.d"
+APACHE_USER="apache"
+COMMENT_OUT_DEFLATE=
+
+# Make everything happen in the OUTPUTDIR.
+cd "${OUTPUTDIR}"
+
+case "$TARGETARCH" in
+ ia32 )
+ export APACHE_MODULEDIR="/usr/lib/httpd/modules"
+ export HOST_ARCH="i386"
+ stage_install_rpm
+ ;;
+ x64 )
+ export APACHE_MODULEDIR="/usr/lib64/httpd/modules"
+ export HOST_ARCH="x86_64"
+ stage_install_rpm
+ ;;
+ * )
+ echo
+ echo "ERROR: Don't know how to build RPMs for '$TARGETARCH'."
+ echo
+ exit 1
+ ;;
+esac
+
+do_package "$HOST_ARCH"
--- /dev/null
+#------------------------------------------------------------------------------
+# mod-spdy.spec
+#------------------------------------------------------------------------------
+
+#------------------------------------------------------------------------------
+# Prologue information
+#------------------------------------------------------------------------------
+Summary : @@SHORTDESC@@
+License : Apache Software License
+Name : @@PACKAGE@@-@@CHANNEL@@
+Version : @@VERSION@@
+Release : @@REVISION@@
+Group : System Environment/Daemons
+Vendor : @@COMPANY_FULLNAME@@
+Url : @@PRODUCTURL@@
+Packager : @@MAINTNAME@@ <@@MAINTMAIL@@>
+
+#------------------------------------------------------------------------------
+# Tested on:
+# TODO
+#------------------------------------------------------------------------------
+
+Provides : @@PROVIDES@@ = %{version}
+Requires : @@DEPENDS@@
+Conflicts : @@REPLACES@@
+
+#------------------------------------------------------------------------------
+# Description
+#------------------------------------------------------------------------------
+%Description
+@@FULLDESC@@
+
+#------------------------------------------------------------------------------
+# Build rule - How to make the package
+#------------------------------------------------------------------------------
+%build
+
+#------------------------------------------------------------------------------
+# Installation rule - how to install it (note that it
+# gets installed into a temp directory given by $RPM_BUILD_ROOT)
+#------------------------------------------------------------------------------
+%install
+rm -rf "$RPM_BUILD_ROOT"
+
+if [ -z "@@STAGEDIR@@" -o ! -d "@@STAGEDIR@@" ] ; then
+ echo "@@STAGEDIR@@ appears to be incorrectly set - aborting"
+ exit 1
+fi
+
+install -m 755 -d \
+ "$RPM_BUILD_ROOT/etc" \
+ "$RPM_BUILD_ROOT/usr"
+# This is hard coded for now
+cp -a "@@STAGEDIR@@/etc/" "$RPM_BUILD_ROOT/"
+cp -a "@@STAGEDIR@@/usr/" "$RPM_BUILD_ROOT/"
+
+#------------------------------------------------------------------------------
+# Rule to clean up a build
+#------------------------------------------------------------------------------
+%clean
+rm -rf "$RPM_BUILD_ROOT"
+
+#------------------------------------------------------------------------------
+# Files listing.
+#------------------------------------------------------------------------------
+%files
+%defattr(-,root,root)
+@@APACHE_MODULEDIR@@/mod_spdy.so
+@@APACHE_MODULEDIR@@/mod_ssl_with_npn.so
+%config(noreplace) @@APACHE_CONFDIR@@/spdy.conf
+%config @@APACHE_CONFDIR@@/load_ssl_with_npn.conf
+/etc/cron.daily/mod-spdy
+
+#------------------------------------------------------------------------------
+# Pre install script
+#------------------------------------------------------------------------------
+%pre
+
+exit 0
+
+#------------------------------------------------------------------------------
+# Post install script
+#------------------------------------------------------------------------------
+%post
+
+@@include@@../common/rpm.include
+
+MODSPDY_ENABLE_UPDATES=@@MODSPDY_ENABLE_UPDATES@@
+
+DEFAULTS_FILE="/etc/default/@@PACKAGE@@"
+if [ -n "${MODSPDY_ENABLE_UPDATES}" -a ! -e "$DEFAULTS_FILE" ]; then
+ echo 'repo_add_once="true"' > "$DEFAULTS_FILE"
+fi
+
+if [ -e "$DEFAULTS_FILE" ]; then
+. "$DEFAULTS_FILE"
+
+if [ "$repo_add_once" = "true" ]; then
+ determine_rpm_package_manager
+
+ case $PACKAGEMANAGER in
+ "yum")
+ install_yum
+ ;;
+ "urpmi")
+ install_urpmi
+ ;;
+ "yast")
+ install_yast
+ ;;
+ esac
+fi
+
+# Some package managers have locks that prevent everything from being
+# configured at install time, so wait a bit then kick the cron job to do
+# whatever is left. Probably the db will be unlocked by then, but if not, the
+# cron job will keep retrying.
+# Do this with 'at' instead of a backgrounded shell because zypper waits on all
+# sub-shells to finish before it finishes, which is exactly the opposite of
+# what we want here. Also preemptively start atd because for some reason it's
+# not always running, which kind of defeats the purpose of having 'at' as a
+# required LSB command.
+service atd start
+echo "sh /etc/cron.daily/@@PACKAGE@@" | at now + 2 minute
+fi
+
+# Turn off loading of the normal mod_ssl.so:
+sed --in-place \
+ 's/^ *LoadModule \+ssl_module .*$/#& # See load_ssl_with_npn.conf/' \
+ @@APACHE_CONFDIR@@/ssl.conf
+
+exit 0
+
+
+#------------------------------------------------------------------------------
+# Pre uninstallation script
+#------------------------------------------------------------------------------
+%preun
+
+if [ "$1" -eq "0" ]; then
+ mode="uninstall"
+elif [ "$1" -eq "1" ]; then
+ mode="upgrade"
+fi
+
+@@include@@../common/rpm.include
+
+# On Debian we only remove when we purge. However, RPM has no equivalent to
+# dpkg --purge, so this is all disabled.
+#
+#determine_rpm_package_manager
+#
+#case $PACKAGEMANAGER in
+#"yum")
+# remove_yum
+# ;;
+#"urpmi")
+# remove_urpmi
+# ;;
+#"yast")
+# remove_yast
+# ;;
+#esac
+
+if [ "$mode" == "uninstall" ]; then
+ # Re-enable loading of the normal mod_ssl.so:
+ sed --in-place \
+ 's/^#\( *LoadModule.*[^ ]\) *# See load_ssl_with_npn.conf$/\1/' \
+ @@APACHE_CONFDIR@@/ssl.conf
+fi
+
+exit 0
+
+#------------------------------------------------------------------------------
+# Post uninstallation script
+#------------------------------------------------------------------------------
+%postun
+
+exit 0
--- /dev/null
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ 'chromium_root': '<(DEPTH)/third_party/chromium/src',
+ },
+ 'targets': [
+ {
+ 'target_name': 'instaweb_util',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ # TODO(mdsteele): Add sources here as we need them.
+ 'instaweb/util/function.cc',
+ ],
+ },
+ {
+ 'target_name': 'spdy',
+ 'type': '<(library)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ '<(chromium_root)',
+ ],
+ 'sources': [
+ '<(chromium_root)/net/spdy/buffered_spdy_framer.cc',
+ '<(chromium_root)/net/spdy/spdy_frame_builder.cc',
+ '<(chromium_root)/net/spdy/spdy_frame_reader.cc',
+ '<(chromium_root)/net/spdy/spdy_framer.cc',
+ '<(chromium_root)/net/spdy/spdy_protocol.cc',
+ ],
+ },
+ ],
+}
--- /dev/null
+#!/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()
+
+#=============================================================================#
--- /dev/null
+Index: modules/ssl/ssl_private.h
+===================================================================
+--- modules/ssl/ssl_private.h (revision 1585744)
++++ modules/ssl/ssl_private.h (working copy)
+@@ -653,6 +653,7 @@
+ #ifndef OPENSSL_NO_TLSEXT
+ int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
+ #endif
++int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg);
+
+ /** Session Cache Support */
+ void ssl_scache_init(server_rec *, apr_pool_t *);
+Index: modules/ssl/ssl_engine_init.c
+===================================================================
+--- modules/ssl/ssl_engine_init.c (revision 1585744)
++++ modules/ssl/ssl_engine_init.c (working copy)
+@@ -654,6 +654,11 @@
+ #endif
+
+ 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 1585744)
++++ 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;
+
+ /*
+@@ -1451,6 +1452,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;
+ }
+
+@@ -1795,6 +1817,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 1585744)
++++ modules/ssl/ssl_engine_kernel.c (working copy)
+@@ -2141,3 +2141,84 @@
+ return 0;
+ }
+ #endif
++
++#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 1585744)
++++ modules/ssl/mod_ssl.c (working copy)
+@@ -237,6 +237,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 1585744)
++++ 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_toolkit_compat.h
+===================================================================
+--- modules/ssl/ssl_toolkit_compat.h (revision 1585744)
++++ modules/ssl/ssl_toolkit_compat.h (working copy)
+@@ -151,6 +151,11 @@
+ #define HAVE_FIPS
+ #endif
+
++#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_NEXTPROTONEG) \
++ && !defined(OPENSSL_NO_TLSEXT)
++#define HAVE_TLS_NPN
++#endif
++
+ #ifndef PEM_F_DEF_CALLBACK
+ #ifdef PEM_F_PEM_DEF_CALLBACK
+ /** In OpenSSL 0.9.8 PEM_F_DEF_CALLBACK was renamed */
--- /dev/null
+# Copyright 2010 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.
+
+
+{
+ 'variables': {
+ 'apr_root': '<(DEPTH)/third_party/apache/apr',
+ 'apr_src_root': '<(apr_root)/src',
+ 'apr_gen_os_root': '<(apr_root)/gen/arch/<(OS)',
+ 'apr_gen_arch_root': '<(apr_gen_os_root)/<(target_arch)',
+ 'system_include_path_apr%': '/usr/include/apr-1.0',
+ 'conditions': [
+ ['OS!="win"', {
+ 'apr_os_include': '<(apr_src_root)/include/arch/unix',
+ }, { # else, OS=="win"
+ 'apr_os_include': '<(apr_src_root)/include/arch/win32',
+ }]
+ ],
+ },
+ 'conditions': [
+ ['use_system_apache_dev==0', {
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(apr_src_root)/include',
+ '<(apr_os_include)',
+ '<(apr_gen_arch_root)/include',
+ ],
+ 'conditions': [
+ ['OS=="mac"', {
+ 'defines': [
+ 'HAVE_CONFIG_H',
+ 'DARWIN',
+ 'SIGPROCMASK_SETS_THREAD_MASK',
+ ]}],
+ ['OS=="linux"', {
+ 'defines': [
+ # We need to define _LARGEFILE64_SOURCE so <sys/types.h>
+ # provides off64_t.
+ '_LARGEFILE64_SOURCE',
+ 'HAVE_CONFIG_H',
+ 'LINUX=2',
+ '_REENTRANT',
+ '_GNU_SOURCE',
+ ],
+ }],
+ ],
+ },
+ },
+ {
+ 'target_name': 'apr',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'include',
+ ],
+ 'export_dependent_settings': [
+ 'include',
+ ],
+ 'sources': [
+ '<(apr_src_root)/passwd/apr_getpass.c',
+ '<(apr_src_root)/strings/apr_strnatcmp.c',
+ '<(apr_src_root)/strings/apr_strtok.c',
+ '<(apr_src_root)/strings/apr_strings.c',
+ '<(apr_src_root)/strings/apr_snprintf.c',
+ '<(apr_src_root)/strings/apr_fnmatch.c',
+ '<(apr_src_root)/strings/apr_cpystrn.c',
+ '<(apr_src_root)/tables/apr_tables.c',
+ '<(apr_src_root)/tables/apr_hash.c',
+ ],
+ 'conditions': [
+ ['OS!="win"', { # TODO(lsong): Add win sources.
+ 'conditions': [
+ ['OS=="linux"', {
+ 'cflags': [
+ '-pthread',
+ '-Wall',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-ldl',
+ ]},
+ }],
+ ],
+ 'sources': [
+ 'src/atomic/unix/builtins.c',
+ 'src/atomic/unix/ia32.c',
+ 'src/atomic/unix/mutex.c',
+ 'src/atomic/unix/ppc.c',
+ 'src/atomic/unix/s390.c',
+ 'src/atomic/unix/solaris.c',
+ 'src/dso/unix/dso.c',
+ 'src/file_io/unix/buffer.c',
+ 'src/file_io/unix/copy.c',
+ 'src/file_io/unix/dir.c',
+ 'src/file_io/unix/fileacc.c',
+ 'src/file_io/unix/filedup.c',
+ 'src/file_io/unix/filepath.c',
+ 'src/file_io/unix/filepath_util.c',
+ 'src/file_io/unix/filestat.c',
+ 'src/file_io/unix/flock.c',
+ 'src/file_io/unix/fullrw.c',
+ 'src/file_io/unix/mktemp.c',
+ 'src/file_io/unix/open.c',
+ 'src/file_io/unix/pipe.c',
+ 'src/file_io/unix/readwrite.c',
+ 'src/file_io/unix/seek.c',
+ 'src/file_io/unix/tempdir.c',
+ 'src/locks/unix/global_mutex.c',
+ 'src/locks/unix/proc_mutex.c',
+ 'src/locks/unix/thread_cond.c',
+ 'src/locks/unix/thread_mutex.c',
+ 'src/locks/unix/thread_rwlock.c',
+ 'src/memory/unix/apr_pools.c',
+ 'src/misc/unix/charset.c',
+ 'src/misc/unix/env.c',
+ 'src/misc/unix/errorcodes.c',
+ 'src/misc/unix/getopt.c',
+ 'src/misc/unix/otherchild.c',
+ 'src/misc/unix/rand.c',
+ 'src/misc/unix/start.c',
+ 'src/misc/unix/version.c',
+ 'src/mmap/unix/common.c',
+ 'src/mmap/unix/mmap.c',
+ 'src/network_io/unix/inet_ntop.c',
+ 'src/network_io/unix/inet_pton.c',
+ 'src/network_io/unix/multicast.c',
+ 'src/network_io/unix/sendrecv.c',
+ 'src/network_io/unix/sockaddr.c',
+ 'src/network_io/unix/socket_util.c',
+ 'src/network_io/unix/sockets.c',
+ 'src/network_io/unix/sockopt.c',
+ 'src/poll/unix/epoll.c',
+ 'src/poll/unix/kqueue.c',
+ 'src/poll/unix/poll.c',
+ 'src/poll/unix/pollcb.c',
+ 'src/poll/unix/pollset.c',
+ 'src/poll/unix/port.c',
+ 'src/poll/unix/select.c',
+ 'src/random/unix/apr_random.c',
+ 'src/random/unix/sha2.c',
+ 'src/random/unix/sha2_glue.c',
+ 'src/shmem/unix/shm.c',
+ 'src/support/unix/waitio.c',
+ 'src/threadproc/unix/proc.c',
+ 'src/threadproc/unix/procsup.c',
+ 'src/threadproc/unix/signals.c',
+ 'src/threadproc/unix/thread.c',
+ 'src/threadproc/unix/threadpriv.c',
+ 'src/time/unix/time.c',
+ 'src/time/unix/timestr.c',
+ 'src/user/unix/groupinfo.c',
+ 'src/user/unix/userinfo.c',
+ ],
+ }],
+ ],
+ }
+ ],
+ },
+ { # use_system_apache_dev
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(system_include_path_apr)',
+ ],
+ 'defines': [
+ # We need to define _LARGEFILE64_SOURCE so <sys/types.h>
+ # provides off64_t.
+ '_LARGEFILE64_SOURCE',
+ 'HAVE_CONFIG_H',
+ 'LINUX=2',
+ '_REENTRANT',
+ '_GNU_SOURCE',
+ ],
+ },
+ },
+ {
+ 'target_name': 'apr',
+ 'type': 'settings',
+ 'dependencies': [
+ 'include',
+ ],
+ 'export_dependent_settings': [
+ 'include',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lapr-1',
+ ],
+ },
+ },
+ ],
+ }],
+ ],
+}
+
--- /dev/null
+/* 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.
+ */
+
+
+#ifndef APR_H
+#define APR_H
+
+/* GENERATED FILE WARNING! DO NOT EDIT apr.h
+ *
+ * You must modify apr.h.in instead.
+ *
+ * And please, make an effort to stub apr.hw and apr.hnw in the process.
+ */
+
+/**
+ * @file apr.h
+ * @brief APR Platform Definitions
+ * @remark This is a generated header generated from include/apr.h.in by
+ * ./configure, or copied from include/apr.hw or include/apr.hnw
+ * for Win32 or Netware by those build environments, respectively.
+ */
+
+/**
+ * @defgroup APR Apache Portability Runtime library
+ * @{
+ */
+/**
+ * @defgroup apr_platform Platform Definitions
+ * @{
+ * @warning
+ * <strong><em>The actual values of macros and typedefs on this page<br>
+ * are platform specific and should NOT be relied upon!</em></strong>
+ */
+
+/* So that we can use inline on some critical functions, and use
+ * GNUC attributes (such as to get -Wall warnings for printf-like
+ * functions). Only do this in gcc 2.7 or later ... it may work
+ * on earlier stuff, but why chance it.
+ *
+ * We've since discovered that the gcc shipped with NeXT systems
+ * as "cc" is completely broken. It claims to be __GNUC__ and so
+ * on, but it doesn't implement half of the things that __GNUC__
+ * means. In particular it's missing inline and the __attribute__
+ * stuff. So we hack around it. PR#1613. -djg
+ */
+#if !defined(__GNUC__) || __GNUC__ < 2 || \
+ (__GNUC__ == 2 && __GNUC_MINOR__ < 7) ||\
+ defined(NEXT)
+#ifndef __attribute__
+#define __attribute__(__x)
+#endif
+#define APR_INLINE
+#define APR_HAS_INLINE 0
+#else
+#define APR_INLINE __inline__
+#define APR_HAS_INLINE 1
+#endif
+
+#define APR_HAVE_ARPA_INET_H 1
+#define APR_HAVE_CONIO_H 0
+#define APR_HAVE_CRYPT_H 1
+#define APR_HAVE_CTYPE_H 1
+#define APR_HAVE_DIRENT_H 1
+#define APR_HAVE_ERRNO_H 1
+#define APR_HAVE_FCNTL_H 1
+#define APR_HAVE_IO_H 0
+#define APR_HAVE_LIMITS_H 1
+#define APR_HAVE_NETDB_H 1
+#define APR_HAVE_NETINET_IN_H 1
+#define APR_HAVE_NETINET_SCTP_H 0
+#define APR_HAVE_NETINET_SCTP_UIO_H 0
+#define APR_HAVE_NETINET_TCP_H 1
+#define APR_HAVE_PTHREAD_H 1
+#define APR_HAVE_SEMAPHORE_H 1
+#define APR_HAVE_SIGNAL_H 1
+#define APR_HAVE_STDARG_H 1
+#define APR_HAVE_STDINT_H 1
+#define APR_HAVE_STDIO_H 1
+#define APR_HAVE_STDLIB_H 1
+#define APR_HAVE_STRING_H 1
+#define APR_HAVE_STRINGS_H 1
+#define APR_HAVE_SYS_IOCTL_H 1
+#define APR_HAVE_SYS_SENDFILE_H 1
+#define APR_HAVE_SYS_SIGNAL_H 1
+#define APR_HAVE_SYS_SOCKET_H 1
+#define APR_HAVE_SYS_SOCKIO_H 0
+#define APR_HAVE_SYS_SYSLIMITS_H 0
+#define APR_HAVE_SYS_TIME_H 1
+#define APR_HAVE_SYS_TYPES_H 1
+#define APR_HAVE_SYS_UIO_H 1
+#define APR_HAVE_SYS_UN_H 1
+#define APR_HAVE_SYS_WAIT_H 1
+#define APR_HAVE_TIME_H 1
+#define APR_HAVE_UNISTD_H 1
+#define APR_HAVE_WINDOWS_H 0
+#define APR_HAVE_WINSOCK2_H 0
+
+/** @} */
+/** @} */
+
+/* We don't include our conditional headers within the doxyblocks
+ * or the extern "C" namespace
+ */
+
+#if APR_HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#if APR_HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#if defined(__cplusplus) && !defined(__STDC_CONSTANT_MACROS)
+/* C99 7.18.4 requires that stdint.h only exposes INT64_C
+ * and UINT64_C for C++ implementations if this is defined: */
+#define __STDC_CONSTANT_MACROS
+#endif
+
+#if APR_HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#if APR_HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef OS2
+#define INCL_DOS
+#define INCL_DOSERRORS
+#include <os2.h>
+#endif
+
+/* header files for PATH_MAX, _POSIX_PATH_MAX */
+#if APR_HAVE_LIMITS_H
+#include <limits.h>
+#else
+#if APR_HAVE_SYS_SYSLIMITS_H
+#include <sys/syslimits.h>
+#endif
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup apr_platform
+ * @ingroup APR
+ * @{
+ */
+
+#define APR_HAVE_SHMEM_MMAP_TMP 1
+#define APR_HAVE_SHMEM_MMAP_SHM 1
+#define APR_HAVE_SHMEM_MMAP_ZERO 1
+#define APR_HAVE_SHMEM_SHMGET_ANON 1
+#define APR_HAVE_SHMEM_SHMGET 1
+#define APR_HAVE_SHMEM_MMAP_ANON 1
+#define APR_HAVE_SHMEM_BEOS 0
+
+#define APR_USE_SHMEM_MMAP_TMP 0
+#define APR_USE_SHMEM_MMAP_SHM 0
+#define APR_USE_SHMEM_MMAP_ZERO 0
+#define APR_USE_SHMEM_SHMGET_ANON 0
+#define APR_USE_SHMEM_SHMGET 1
+#define APR_USE_SHMEM_MMAP_ANON 1
+#define APR_USE_SHMEM_BEOS 0
+
+#define APR_USE_FLOCK_SERIALIZE 0
+#define APR_USE_SYSVSEM_SERIALIZE 1
+#define APR_USE_POSIXSEM_SERIALIZE 0
+#define APR_USE_FCNTL_SERIALIZE 0
+#define APR_USE_PROC_PTHREAD_SERIALIZE 0
+#define APR_USE_PTHREAD_SERIALIZE 1
+
+#define APR_HAS_FLOCK_SERIALIZE 1
+#define APR_HAS_SYSVSEM_SERIALIZE 1
+#define APR_HAS_POSIXSEM_SERIALIZE 1
+#define APR_HAS_FCNTL_SERIALIZE 1
+#define APR_HAS_PROC_PTHREAD_SERIALIZE 1
+
+#define APR_PROCESS_LOCK_IS_GLOBAL 0
+
+#define APR_HAVE_CORKABLE_TCP 1
+#define APR_HAVE_GETRLIMIT 1
+#define APR_HAVE_IN_ADDR 1
+#define APR_HAVE_INET_ADDR 1
+#define APR_HAVE_INET_NETWORK 1
+#define APR_HAVE_IPV6 1
+#define APR_HAVE_MEMMOVE 1
+#define APR_HAVE_SETRLIMIT 1
+#define APR_HAVE_SIGACTION 1
+#define APR_HAVE_SIGSUSPEND 1
+#define APR_HAVE_SIGWAIT 1
+#define APR_HAVE_SA_STORAGE 1
+#define APR_HAVE_STRCASECMP 1
+#define APR_HAVE_STRDUP 1
+#define APR_HAVE_STRICMP 0
+#define APR_HAVE_STRNCASECMP 1
+#define APR_HAVE_STRNICMP 0
+#define APR_HAVE_STRSTR 1
+#define APR_HAVE_MEMCHR 1
+#define APR_HAVE_STRUCT_RLIMIT 1
+#define APR_HAVE_UNION_SEMUN 0
+#define APR_HAVE_SCTP 0
+#define APR_HAVE_IOVEC 1
+
+/* APR Feature Macros */
+#define APR_HAS_SHARED_MEMORY 1
+#define APR_HAS_THREADS 1
+#define APR_HAS_SENDFILE 1
+#define APR_HAS_MMAP 1
+#define APR_HAS_FORK 1
+#define APR_HAS_RANDOM 1
+#define APR_HAS_OTHER_CHILD 1
+#define APR_HAS_DSO 1
+#define APR_HAS_SO_ACCEPTFILTER 0
+#define APR_HAS_UNICODE_FS 0
+#define APR_HAS_PROC_INVOKED 0
+#define APR_HAS_USER 1
+#define APR_HAS_LARGE_FILES 1
+#define APR_HAS_XTHREAD_FILES 0
+#define APR_HAS_OS_UUID 0
+
+#define APR_PROCATTR_USER_SET_REQUIRES_PASSWORD 0
+
+/* APR sets APR_FILES_AS_SOCKETS to 1 on systems where it is possible
+ * to poll on files/pipes.
+ */
+#define APR_FILES_AS_SOCKETS 1
+
+/* This macro indicates whether or not EBCDIC is the native character set.
+ */
+#define APR_CHARSET_EBCDIC 0
+
+/* If we have a TCP implementation that can be "corked", what flag
+ * do we use?
+ */
+#define APR_TCP_NOPUSH_FLAG TCP_CORK
+
+/* Is the TCP_NODELAY socket option inherited from listening sockets?
+*/
+#define APR_TCP_NODELAY_INHERITED 1
+
+/* Is the O_NONBLOCK flag inherited from listening sockets?
+*/
+#define APR_O_NONBLOCK_INHERITED 0
+
+/* Typedefs that APR needs. */
+
+typedef unsigned char apr_byte_t;
+
+typedef short apr_int16_t;
+typedef unsigned short apr_uint16_t;
+
+typedef int apr_int32_t;
+typedef unsigned int apr_uint32_t;
+
+typedef long long apr_int64_t;
+typedef unsigned long long apr_uint64_t;
+
+typedef size_t apr_size_t;
+typedef ssize_t apr_ssize_t;
+typedef off64_t apr_off_t;
+typedef socklen_t apr_socklen_t;
+typedef unsigned long apr_ino_t;
+
+#define APR_SIZEOF_VOIDP 4
+
+#if APR_SIZEOF_VOIDP == 8
+typedef apr_uint64_t apr_uintptr_t;
+#else
+typedef apr_uint32_t apr_uintptr_t;
+#endif
+
+/* Are we big endian? */
+#define APR_IS_BIGENDIAN 0
+
+/* Mechanisms to properly type numeric literals */
+#define APR_INT64_C(val) INT64_C(val)
+#define APR_UINT64_C(val) UINT64_C(val)
+
+#ifdef INT16_MIN
+#define APR_INT16_MIN INT16_MIN
+#else
+#define APR_INT16_MIN (-0x7fff - 1)
+#endif
+
+#ifdef INT16_MAX
+#define APR_INT16_MAX INT16_MAX
+#else
+#define APR_INT16_MAX (0x7fff)
+#endif
+
+#ifdef UINT16_MAX
+#define APR_UINT16_MAX UINT16_MAX
+#else
+#define APR_UINT16_MAX (0xffff)
+#endif
+
+#ifdef INT32_MIN
+#define APR_INT32_MIN INT32_MIN
+#else
+#define APR_INT32_MIN (-0x7fffffff - 1)
+#endif
+
+#ifdef INT32_MAX
+#define APR_INT32_MAX INT32_MAX
+#else
+#define APR_INT32_MAX 0x7fffffff
+#endif
+
+#ifdef UINT32_MAX
+#define APR_UINT32_MAX UINT32_MAX
+#else
+#define APR_UINT32_MAX (0xffffffffU)
+#endif
+
+#ifdef INT64_MIN
+#define APR_INT64_MIN INT64_MIN
+#else
+#define APR_INT64_MIN (APR_INT64_C(-0x7fffffffffffffff) - 1)
+#endif
+
+#ifdef INT64_MAX
+#define APR_INT64_MAX INT64_MAX
+#else
+#define APR_INT64_MAX APR_INT64_C(0x7fffffffffffffff)
+#endif
+
+#ifdef UINT64_MAX
+#define APR_UINT64_MAX UINT64_MAX
+#else
+#define APR_UINT64_MAX APR_UINT64_C(0xffffffffffffffff)
+#endif
+
+#define APR_SIZE_MAX (~((apr_size_t)0))
+
+
+/* Definitions that APR programs need to work properly. */
+
+/**
+ * APR public API wrap for C++ compilers.
+ */
+#ifdef __cplusplus
+#define APR_BEGIN_DECLS extern "C" {
+#define APR_END_DECLS }
+#else
+#define APR_BEGIN_DECLS
+#define APR_END_DECLS
+#endif
+
+/**
+ * Thread callbacks from APR functions must be declared with APR_THREAD_FUNC,
+ * so that they follow the platform's calling convention.
+ * <PRE>
+ *
+ * void* APR_THREAD_FUNC my_thread_entry_fn(apr_thread_t *thd, void *data);
+ *
+ * </PRE>
+ */
+#define APR_THREAD_FUNC
+
+/**
+ * The public APR functions are declared with APR_DECLARE(), so they may
+ * use the most appropriate calling convention. Public APR functions with
+ * variable arguments must use APR_DECLARE_NONSTD().
+ *
+ * @remark Both the declaration and implementations must use the same macro.
+ *
+ * <PRE>
+ * APR_DECLARE(rettype) apr_func(args)
+ * </PRE>
+ * @see APR_DECLARE_NONSTD @see APR_DECLARE_DATA
+ * @remark Note that when APR compiles the library itself, it passes the
+ * symbol -DAPR_DECLARE_EXPORT to the compiler on some platforms (e.g. Win32)
+ * to export public symbols from the dynamic library build.\n
+ * The user must define the APR_DECLARE_STATIC when compiling to target
+ * the static APR library on some platforms (e.g. Win32.) The public symbols
+ * are neither exported nor imported when APR_DECLARE_STATIC is defined.\n
+ * By default, compiling an application and including the APR public
+ * headers, without defining APR_DECLARE_STATIC, will prepare the code to be
+ * linked to the dynamic library.
+ */
+#define APR_DECLARE(type) type
+
+/**
+ * The public APR functions using variable arguments are declared with
+ * APR_DECLARE_NONSTD(), as they must follow the C language calling convention.
+ * @see APR_DECLARE @see APR_DECLARE_DATA
+ * @remark Both the declaration and implementations must use the same macro.
+ * <PRE>
+ *
+ * APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ *
+ * </PRE>
+ */
+#define APR_DECLARE_NONSTD(type) type
+
+/**
+ * The public APR variables are declared with AP_MODULE_DECLARE_DATA.
+ * This assures the appropriate indirection is invoked at compile time.
+ * @see APR_DECLARE @see APR_DECLARE_NONSTD
+ * @remark Note that the declaration and implementations use different forms,
+ * but both must include the macro.
+ *
+ * <PRE>
+ *
+ * extern APR_DECLARE_DATA type apr_variable;\n
+ * APR_DECLARE_DATA type apr_variable = value;
+ *
+ * </PRE>
+ */
+#define APR_DECLARE_DATA
+
+/* Define APR_SSIZE_T_FMT.
+ * If ssize_t is an integer we define it to be "d",
+ * if ssize_t is a long int we define it to be "ld",
+ * if ssize_t is neither we declare an error here.
+ * I looked for a better way to define this here, but couldn't find one, so
+ * to find the logic for this definition search for "ssize_t_fmt" in
+ * configure.in.
+ */
+#define APR_SSIZE_T_FMT "d"
+
+/* And APR_SIZE_T_FMT */
+#define APR_SIZE_T_FMT "u"
+
+/* And APR_OFF_T_FMT */
+#define APR_OFF_T_FMT APR_INT64_T_FMT
+
+/* And APR_PID_T_FMT */
+#define APR_PID_T_FMT "d"
+
+/* And APR_INT64_T_FMT */
+#define APR_INT64_T_FMT "lld"
+
+/* And APR_UINT64_T_FMT */
+#define APR_UINT64_T_FMT "llu"
+
+/* And APR_UINT64_T_HEX_FMT */
+#define APR_UINT64_T_HEX_FMT "llx"
+
+/* Does the proc mutex lock threads too */
+#define APR_PROC_MUTEX_IS_GLOBAL 0
+
+/* Local machine definition for console and log output. */
+#define APR_EOL_STR "\n"
+
+#if APR_HAVE_SYS_WAIT_H
+#ifdef WEXITSTATUS
+#define apr_wait_t int
+#else
+#define apr_wait_t union wait
+#define WEXITSTATUS(status) (int)((status).w_retcode)
+#define WTERMSIG(status) (int)((status).w_termsig)
+#endif /* !WEXITSTATUS */
+#elif defined(__MINGW32__)
+typedef int apr_wait_t;
+#endif /* HAVE_SYS_WAIT_H */
+
+#if defined(PATH_MAX)
+#define APR_PATH_MAX PATH_MAX
+#elif defined(_POSIX_PATH_MAX)
+#define APR_PATH_MAX _POSIX_PATH_MAX
+#else
+#error no decision has been made on APR_PATH_MAX for your platform
+#endif
+
+#define APR_DSOPATH "LD_LIBRARY_PATH"
+
+/** @} */
+
+/* Definitions that only Win32 programs need to compile properly. */
+
+/* XXX These simply don't belong here, perhaps in apr_portable.h
+ * based on some APR_HAVE_PID/GID/UID?
+ */
+#ifdef __MINGW32__
+#ifndef __GNUC__
+typedef int pid_t;
+#endif
+typedef int uid_t;
+typedef int gid_t;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APR_H */
--- /dev/null
+/* include/arch/unix/apr_private.h. Generated from apr_private.h.in by configure. */
+/* include/arch/unix/apr_private.h.in. Generated from configure.in by autoheader. */
+
+
+#ifndef APR_PRIVATE_H
+#define APR_PRIVATE_H
+
+
+/* Define as function which can be used for conversion of strings to
+ apr_int64_t */
+#define APR_INT64_STRFN strtoll
+
+/* Define as function used for conversion of strings to apr_off_t */
+#define APR_OFF_T_STRFN strtoll
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to path of random device */
+#define DEV_RANDOM "/dev/urandom"
+
+/* Define if struct dirent has an inode member */
+#define DIRENT_INODE d_fileno
+
+/* Define if struct dirent has a d_type member */
+#define DIRENT_TYPE d_type
+
+/* Define if DSO support uses dlfcn.h */
+#define DSO_USE_DLFCN 1
+
+/* Define if DSO support uses dyld.h */
+/* #undef DSO_USE_DYLD */
+
+/* Define if DSO support uses shl_load */
+/* #undef DSO_USE_SHL */
+
+/* Define to list of paths to EGD sockets */
+/* #undef EGD_DEFAULT_SOCKET */
+
+/* Define if fcntl locks affect threads within the process */
+/* #undef FCNTL_IS_GLOBAL */
+
+/* Define if fcntl returns EACCES when F_SETLK is already held */
+/* #undef FCNTL_TRYACQUIRE_EACCES */
+
+/* Define if flock locks affect threads within the process */
+/* #undef FLOCK_IS_GLOBAL */
+
+/* Define if gethostbyaddr is thread safe */
+/* #undef GETHOSTBYADDR_IS_THREAD_SAFE */
+
+/* Define if gethostbyname is thread safe */
+/* #undef GETHOSTBYNAME_IS_THREAD_SAFE */
+
+/* Define if gethostbyname_r has the glibc style */
+#define GETHOSTBYNAME_R_GLIBC2 1
+
+/* Define if gethostbyname_r has the hostent_data for the third argument */
+/* #undef GETHOSTBYNAME_R_HOSTENT_DATA */
+
+/* Define if getservbyname is thread safe */
+/* #undef GETSERVBYNAME_IS_THREAD_SAFE */
+
+/* Define if getservbyname_r has the glibc style */
+#define GETSERVBYNAME_R_GLIBC2 1
+
+/* Define if getservbyname_r has the OSF/1 style */
+/* #undef GETSERVBYNAME_R_OSF1 */
+
+/* Define if getservbyname_r has the Solaris style */
+/* #undef GETSERVBYNAME_R_SOLARIS */
+
+/* Define if accept4 function is supported */
+/* #undef HAVE_ACCEPT4 */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#define HAVE_ARPA_INET_H 1
+
+/* Define if compiler provides atomic builtins */
+#define HAVE_ATOMIC_BUILTINS 1
+
+/* Define if BONE_VERSION is defined in sys/socket.h */
+/* #undef HAVE_BONE_VERSION */
+
+/* Define to 1 if you have the <ByteOrder.h> header file. */
+/* #undef HAVE_BYTEORDER_H */
+
+/* Define to 1 if you have the `calloc' function. */
+#define HAVE_CALLOC 1
+
+/* Define to 1 if you have the <conio.h> header file. */
+/* #undef HAVE_CONIO_H */
+
+/* Define to 1 if you have the `create_area' function. */
+/* #undef HAVE_CREATE_AREA */
+
+/* Define to 1 if you have the `create_sem' function. */
+/* #undef HAVE_CREATE_SEM */
+
+/* Define to 1 if you have the <crypt.h> header file. */
+#define HAVE_CRYPT_H 1
+
+/* Define to 1 if you have the <ctype.h> header file. */
+#define HAVE_CTYPE_H 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dir.h> header file. */
+/* #undef HAVE_DIR_H */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <dl.h> header file. */
+/* #undef HAVE_DL_H */
+
+/* Define if dup3 function is supported */
+/* #undef HAVE_DUP3 */
+
+/* Define if EGD is supported */
+/* #undef HAVE_EGD */
+
+/* Define if the epoll interface is supported */
+#define HAVE_EPOLL 1
+
+/* Define if epoll_create1 function is supported */
+/* #undef HAVE_EPOLL_CREATE1 */
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdatasync' function. */
+#define HAVE_FDATASYNC 1
+
+/* Define to 1 if you have the `flock' function. */
+#define HAVE_FLOCK 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define if F_SETLK is defined in fcntl.h */
+#define HAVE_F_SETLK 1
+
+/* Define if getaddrinfo accepts the AI_ADDRCONFIG flag */
+#define HAVE_GAI_ADDRCONFIG 1
+
+/* Define to 1 if you have the `gai_strerror' function. */
+#define HAVE_GAI_STRERROR 1
+
+/* Define if getaddrinfo exists and works well enough for APR */
+#define HAVE_GETADDRINFO 1
+
+/* Define to 1 if you have the `getenv' function. */
+#define HAVE_GETENV 1
+
+/* Define to 1 if you have the `getgrgid_r' function. */
+#define HAVE_GETGRGID_R 1
+
+/* Define to 1 if you have the `getgrnam_r' function. */
+#define HAVE_GETGRNAM_R 1
+
+/* Define to 1 if you have the `gethostbyaddr_r' function. */
+#define HAVE_GETHOSTBYADDR_R 1
+
+/* Define to 1 if you have the `gethostbyname_r' function. */
+#define HAVE_GETHOSTBYNAME_R 1
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define HAVE_GETIFADDRS 1
+
+/* Define if getnameinfo exists */
+#define HAVE_GETNAMEINFO 1
+
+/* Define to 1 if you have the `getpass' function. */
+#define HAVE_GETPASS 1
+
+/* Define to 1 if you have the `getpassphrase' function. */
+/* #undef HAVE_GETPASSPHRASE */
+
+/* Define to 1 if you have the `getpwnam_r' function. */
+#define HAVE_GETPWNAM_R 1
+
+/* Define to 1 if you have the `getpwuid_r' function. */
+#define HAVE_GETPWUID_R 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have the `getservbyname_r' function. */
+#define HAVE_GETSERVBYNAME_R 1
+
+/* Define to 1 if you have the `gmtime_r' function. */
+#define HAVE_GMTIME_R 1
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define if hstrerror is present */
+/* #undef HAVE_HSTRERROR */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <io.h> header file. */
+/* #undef HAVE_IO_H */
+
+/* Define to 1 if you have the `isinf' function. */
+#define HAVE_ISINF 1
+
+/* Define to 1 if you have the `isnan' function. */
+#define HAVE_ISNAN 1
+
+/* Define to 1 if you have the <kernel/OS.h> header file. */
+/* #undef HAVE_KERNEL_OS_H */
+
+/* Define to 1 if you have the `kqueue' function. */
+/* #undef HAVE_KQUEUE */
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBADVAPI32 */
+
+/* Define to 1 if you have the `bsd' library (-lbsd). */
+/* #undef HAVE_LIBBSD */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBKERNEL32 */
+
+/* Define to 1 if you have the `msvcrt' library (-lmsvcrt). */
+/* #undef HAVE_LIBMSVCRT */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBRPCRT4 */
+
+/* Define to 1 if you have the `sendfile' library (-lsendfile). */
+/* #undef HAVE_LIBSENDFILE */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBSHELL32 */
+
+/* Define to 1 if you have the `truerand' library (-ltruerand). */
+/* #undef HAVE_LIBTRUERAND */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBWS2_32 */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the `localtime_r' function. */
+#define HAVE_LOCALTIME_R 1
+
+/* Define if LOCK_EX is defined in sys/file.h */
+#define HAVE_LOCK_EX 1
+
+/* Define to 1 if you have the <mach-o/dyld.h> header file. */
+/* #undef HAVE_MACH_O_DYLD_H */
+
+/* Define to 1 if you have the <malloc.h> header file. */
+#define HAVE_MALLOC_H 1
+
+/* Define if MAP_ANON is defined in sys/mman.h */
+#define HAVE_MAP_ANON 1
+
+/* Define to 1 if you have the `memchr' function. */
+#define HAVE_MEMCHR 1
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mkstemp64' function. */
+#define HAVE_MKSTEMP64 1
+
+/* Define to 1 if you have the `mmap' function. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the `mmap64' function. */
+#define HAVE_MMAP64 1
+
+/* Define to 1 if you have the `munmap' function. */
+#define HAVE_MUNMAP 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <netinet/sctp.h> header file. */
+/* #undef HAVE_NETINET_SCTP_H */
+
+/* Define to 1 if you have the <netinet/sctp_uio.h> header file. */
+/* #undef HAVE_NETINET_SCTP_UIO_H */
+
+/* Defined if netinet/tcp.h is present */
+#define HAVE_NETINET_TCP_H 1
+
+/* Define to 1 if you have the <net/errno.h> header file. */
+/* #undef HAVE_NET_ERRNO_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#define HAVE_NL_LANGINFO 1
+
+/* Define to 1 if you have the <os2.h> header file. */
+/* #undef HAVE_OS2_H */
+
+/* Define to 1 if you have the <osreldate.h> header file. */
+/* #undef HAVE_OSRELDATE_H */
+
+/* Define to 1 if you have the <OS.h> header file. */
+/* #undef HAVE_OS_H */
+
+/* Define to 1 if you have the `poll' function. */
+#define HAVE_POLL 1
+
+/* Define if POLLIN is defined */
+#define HAVE_POLLIN 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#define HAVE_POLL_H 1
+
+/* Define to 1 if you have the `port_create' function. */
+/* #undef HAVE_PORT_CREATE */
+
+/* Define to 1 if you have the <process.h> header file. */
+/* #undef HAVE_PROCESS_H */
+
+/* Define to 1 if you have the `pthread_attr_setguardsize' function. */
+#define HAVE_PTHREAD_ATTR_SETGUARDSIZE 1
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* Define to 1 if you have the `pthread_key_delete' function. */
+#define HAVE_PTHREAD_KEY_DELETE 1
+
+/* Define to 1 if you have the `pthread_mutexattr_setpshared' function. */
+#define HAVE_PTHREAD_MUTEXATTR_SETPSHARED 1
+
+/* Define if recursive pthread mutexes are available */
+#define HAVE_PTHREAD_MUTEX_RECURSIVE 1
+
+/* Define if cross-process robust mutexes are available */
+#define HAVE_PTHREAD_MUTEX_ROBUST 1
+
+/* Define if PTHREAD_PROCESS_SHARED is defined in pthread.h */
+#define HAVE_PTHREAD_PROCESS_SHARED 1
+
+/* Define if pthread rwlocks are available */
+#define HAVE_PTHREAD_RWLOCKS 1
+
+/* Define to 1 if you have the `pthread_rwlock_init' function. */
+#define HAVE_PTHREAD_RWLOCK_INIT 1
+
+/* Define to 1 if you have the `pthread_yield' function. */
+#define HAVE_PTHREAD_YIELD 1
+
+/* Define to 1 if you have the `putenv' function. */
+#define HAVE_PUTENV 1
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `readdir64_r' function. */
+#define HAVE_READDIR64_R 1
+
+/* Define to 1 if you have the <sched.h> header file. */
+/* #undef HAVE_SCHED_H */
+
+/* Define to 1 if you have the `sched_yield' function. */
+/* #undef HAVE_SCHED_YIELD */
+
+/* Define to 1 if you have the <semaphore.h> header file. */
+#define HAVE_SEMAPHORE_H 1
+
+/* Define to 1 if you have the `semctl' function. */
+#define HAVE_SEMCTL 1
+
+/* Define to 1 if you have the `semget' function. */
+#define HAVE_SEMGET 1
+
+/* Define to 1 if you have the `sem_close' function. */
+#define HAVE_SEM_CLOSE 1
+
+/* Define to 1 if you have the `sem_post' function. */
+#define HAVE_SEM_POST 1
+
+/* Define if SEM_UNDO is defined in sys/sem.h */
+#define HAVE_SEM_UNDO 1
+
+/* Define to 1 if you have the `sem_unlink' function. */
+#define HAVE_SEM_UNLINK 1
+
+/* Define to 1 if you have the `sem_wait' function. */
+#define HAVE_SEM_WAIT 1
+
+/* Define to 1 if you have the `sendfile' function. */
+#define HAVE_SENDFILE 1
+
+/* Define to 1 if you have the `sendfile64' function. */
+#define HAVE_SENDFILE64 1
+
+/* Define to 1 if you have the `sendfilev' function. */
+/* #undef HAVE_SENDFILEV */
+
+/* Define to 1 if you have the `sendfilev64' function. */
+/* #undef HAVE_SENDFILEV64 */
+
+/* Define to 1 if you have the `send_file' function. */
+/* #undef HAVE_SEND_FILE */
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define to 1 if you have the `set_h_errno' function. */
+/* #undef HAVE_SET_H_ERRNO */
+
+/* Define to 1 if you have the `shmat' function. */
+#define HAVE_SHMAT 1
+
+/* Define to 1 if you have the `shmctl' function. */
+#define HAVE_SHMCTL 1
+
+/* Define to 1 if you have the `shmdt' function. */
+#define HAVE_SHMDT 1
+
+/* Define to 1 if you have the `shmget' function. */
+#define HAVE_SHMGET 1
+
+/* Define to 1 if you have the `shm_open' function. */
+#define HAVE_SHM_OPEN 1
+
+/* Define to 1 if you have the `shm_unlink' function. */
+#define HAVE_SHM_UNLINK 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#define HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the `sigsuspend' function. */
+#define HAVE_SIGSUSPEND 1
+
+/* Define to 1 if you have the `sigwait' function. */
+#define HAVE_SIGWAIT 1
+
+/* Whether you have socklen_t */
+#define HAVE_SOCKLEN_T 1
+
+/* Define if the SOCK_CLOEXEC flag is supported */
+/* #undef HAVE_SOCK_CLOEXEC */
+
+/* Define if SO_ACCEPTFILTER is defined in sys/socket.h */
+/* #undef HAVE_SO_ACCEPTFILTER */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#define HAVE_STDDEF_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror_r' function. */
+#define HAVE_STRERROR_R 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strstr' function. */
+#define HAVE_STRSTR 1
+
+/* Define if struct impreq was found */
+#define HAVE_STRUCT_IPMREQ 1
+
+/* Define to 1 if `st_atimensec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIMENSEC */
+
+/* Define to 1 if `st_atime_n' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIME_N */
+
+/* Define to 1 if `st_atim.tv_nsec' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC 1
+
+/* Define to 1 if `st_blocks' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_BLOCKS 1
+
+/* Define to 1 if `st_ctimensec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIMENSEC */
+
+/* Define to 1 if `st_ctime_n' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIME_N */
+
+/* Define to 1 if `st_ctim.tv_nsec' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC 1
+
+/* Define to 1 if `st_mtimensec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIMENSEC */
+
+/* Define to 1 if `st_mtime_n' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIME_N */
+
+/* Define to 1 if `st_mtim.tv_nsec' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1
+
+/* Define to 1 if `tm_gmtoff' is member of `struct tm'. */
+#define HAVE_STRUCT_TM_TM_GMTOFF 1
+
+/* Define to 1 if `__tm_gmtoff' is member of `struct tm'. */
+/* #undef HAVE_STRUCT_TM___TM_GMTOFF */
+
+/* Define to 1 if you have the <sysapi.h> header file. */
+/* #undef HAVE_SYSAPI_H */
+
+/* Define to 1 if you have the <sysgtime.h> header file. */
+/* #undef HAVE_SYSGTIME_H */
+
+/* Define to 1 if you have the <sys/file.h> header file. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/mutex.h> header file. */
+/* #undef HAVE_SYS_MUTEX_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+#define HAVE_SYS_POLL_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/sem.h> header file. */
+#define HAVE_SYS_SEM_H 1
+
+/* Define to 1 if you have the <sys/sendfile.h> header file. */
+#define HAVE_SYS_SENDFILE_H 1
+
+/* Define to 1 if you have the <sys/shm.h> header file. */
+#define HAVE_SYS_SHM_H 1
+
+/* Define to 1 if you have the <sys/signal.h> header file. */
+#define HAVE_SYS_SIGNAL_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/sockio.h> header file. */
+/* #undef HAVE_SYS_SOCKIO_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysctl.h> header file. */
+#define HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the <sys/syslimits.h> header file. */
+/* #undef HAVE_SYS_SYSLIMITS_H */
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+#define HAVE_SYS_UIO_H 1
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#define HAVE_SYS_UN_H 1
+
+/* Define to 1 if you have the <sys/uuid.h> header file. */
+/* #undef HAVE_SYS_UUID_H */
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define if TCP_CORK is defined in netinet/tcp.h */
+#define HAVE_TCP_CORK 1
+
+/* Define if TCP_NODELAY and TCP_CORK can be enabled at the same time */
+#define HAVE_TCP_NODELAY_WITH_CORK 1
+
+/* Define if TCP_NOPUSH is defined in netinet/tcp.h */
+/* #undef HAVE_TCP_NOPUSH */
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the <tpfeq.h> header file. */
+/* #undef HAVE_TPFEQ_H */
+
+/* Define to 1 if you have the <tpfio.h> header file. */
+/* #undef HAVE_TPFIO_H */
+
+/* Define if truerand is supported */
+/* #undef HAVE_TRUERAND */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <unix.h> header file. */
+/* #undef HAVE_UNIX_H */
+
+/* Define to 1 if you have the `unsetenv' function. */
+#define HAVE_UNSETENV 1
+
+/* Define to 1 if you have the `utime' function. */
+#define HAVE_UTIME 1
+
+/* Define to 1 if you have the `utimes' function. */
+#define HAVE_UTIMES 1
+
+/* Define to 1 if you have the `uuid_create' function. */
+/* #undef HAVE_UUID_CREATE */
+
+/* Define to 1 if you have the `uuid_generate' function. */
+#define HAVE_UUID_GENERATE 1
+
+/* Define to 1 if you have the <uuid.h> header file. */
+/* #undef HAVE_UUID_H */
+
+/* Define to 1 if you have the <uuid/uuid.h> header file. */
+/* #undef HAVE_UUID_UUID_H */
+
+/* Define if C compiler supports VLA */
+#define HAVE_VLA 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if you have the <windows.h> header file. */
+/* #undef HAVE_WINDOWS_H */
+
+/* Define to 1 if you have the <winsock2.h> header file. */
+/* #undef HAVE_WINSOCK2_H */
+
+/* Define to 1 if you have the `writev' function. */
+#define HAVE_WRITEV 1
+
+/* Define for z/OS pthread API nuances */
+/* #undef HAVE_ZOS_PTHREADS */
+
+/* Define if EAI_ error codes from getaddrinfo are negative */
+#define NEGATIVE_EAI 1
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define if POSIX semaphores affect threads within the process */
+/* #undef POSIXSEM_IS_GLOBAL */
+
+/* Define on PowerPC 405 where errata 77 applies */
+/* #undef PPC405_ERRATA */
+
+/* Define if pthread_attr_getdetachstate() has one arg */
+/* #undef PTHREAD_ATTR_GETDETACHSTATE_TAKES_ONE_ARG */
+
+/* Define if pthread_getspecific() has two args */
+/* #undef PTHREAD_GETSPECIFIC_TAKES_TWO_ARGS */
+
+/* Define if readdir is thread safe */
+/* #undef READDIR_IS_THREAD_SAFE */
+
+/* Define to 1 if the `setpgrp' function takes no argument. */
+#define SETPGRP_VOID 1
+
+/* */
+/* #undef SIGWAIT_TAKES_ONE_ARG */
+
+/* The size of `char', as computed by sizeof. */
+#define SIZEOF_CHAR 1
+
+/* The size of `int', as computed by sizeof. */
+#define SIZEOF_INT 4
+
+/* The size of `long', as computed by sizeof. */
+#define SIZEOF_LONG 4
+
+/* The size of `long long', as computed by sizeof. */
+#define SIZEOF_LONG_LONG 8
+
+/* The size of off_t */
+#define SIZEOF_OFF_T 4
+
+/* The size of pid_t */
+#define SIZEOF_PID_T 4
+
+/* The size of `short', as computed by sizeof. */
+#define SIZEOF_SHORT 2
+
+/* The size of size_t */
+#define SIZEOF_SIZE_T 4
+
+/* The size of ssize_t */
+#define SIZEOF_SSIZE_T 4
+
+/* The size of struct iovec */
+#define SIZEOF_STRUCT_IOVEC 8
+
+/* The size of `void*', as computed by sizeof. */
+#define SIZEOF_VOIDP 4
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if strerror returns int */
+/* #undef STRERROR_R_RC_INT */
+
+/* Define if SysV semaphores affect threads within the process */
+/* #undef SYSVSEM_IS_GLOBAL */
+
+/* Define if use of generic atomics is requested */
+#define USE_ATOMICS_GENERIC 1
+
+/* Define if BeOS Semaphores will be used */
+/* #undef USE_BEOSSEM */
+
+/* Define if SVR4-style fcntl() will be used */
+/* #undef USE_FCNTL_SERIALIZE */
+
+/* Define if 4.2BSD-style flock() will be used */
+/* #undef USE_FLOCK_SERIALIZE */
+
+/* Define if BeOS areas will be used */
+/* #undef USE_SHMEM_BEOS */
+
+/* Define if BeOS areas will be used */
+/* #undef USE_SHMEM_BEOS_ANON */
+
+/* Define if 4.4BSD-style mmap() via MAP_ANON will be used */
+#define USE_SHMEM_MMAP_ANON 1
+
+/* Define if mmap() via POSIX.1 shm_open() on temporary file will be used */
+/* #undef USE_SHMEM_MMAP_SHM */
+
+/* Define if Classical mmap() on temporary file will be used */
+/* #undef USE_SHMEM_MMAP_TMP */
+
+/* Define if SVR4-style mmap() on /dev/zero will be used */
+/* #undef USE_SHMEM_MMAP_ZERO */
+
+/* Define if OS/2 DosAllocSharedMem() will be used */
+/* #undef USE_SHMEM_OS2 */
+
+/* Define if OS/2 DosAllocSharedMem() will be used */
+/* #undef USE_SHMEM_OS2_ANON */
+
+/* Define if SysV IPC shmget() will be used */
+#define USE_SHMEM_SHMGET 1
+
+/* Define if SysV IPC shmget() will be used */
+/* #undef USE_SHMEM_SHMGET_ANON */
+
+/* Define if Windows shared memory will be used */
+/* #undef USE_SHMEM_WIN32 */
+
+/* Define if Windows CreateFileMapping() will be used */
+/* #undef USE_SHMEM_WIN32_ANON */
+
+/* Define if SysV IPC semget() will be used */
+#define USE_SYSVSEM_SERIALIZE 1
+
+/* Define if apr_wait_for_io_or_timeout() uses poll(2) */
+#define WAITIO_USES_POLL 1
+
+/* Define to 1 if your processor stores words with the most significant byte
+ first (like Motorola and SPARC, unlike Intel and VAX). */
+/* #undef WORDS_BIGENDIAN */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Enable extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef ssize_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+
+/* switch this on if we have a BeOS version below BONE */
+#if BEOS && !HAVE_BONE_VERSION
+#define BEOS_R5 1
+#else
+#define BEOS_BONE 1
+#endif
+
+/*
+ * Include common private declarations.
+ */
+#include "../apr_private_common.h"
+#endif /* APR_PRIVATE_H */
--- /dev/null
+/* 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.
+ */
+
+
+#ifndef APR_H
+#define APR_H
+
+/* GENERATED FILE WARNING! DO NOT EDIT apr.h
+ *
+ * You must modify apr.h.in instead.
+ *
+ * And please, make an effort to stub apr.hw and apr.hnw in the process.
+ */
+
+/**
+ * @file apr.h
+ * @brief APR Platform Definitions
+ * @remark This is a generated header generated from include/apr.h.in by
+ * ./configure, or copied from include/apr.hw or include/apr.hnw
+ * for Win32 or Netware by those build environments, respectively.
+ */
+
+/**
+ * @defgroup APR Apache Portability Runtime library
+ * @{
+ */
+/**
+ * @defgroup apr_platform Platform Definitions
+ * @{
+ * @warning
+ * <strong><em>The actual values of macros and typedefs on this page<br>
+ * are platform specific and should NOT be relied upon!</em></strong>
+ */
+
+/* So that we can use inline on some critical functions, and use
+ * GNUC attributes (such as to get -Wall warnings for printf-like
+ * functions). Only do this in gcc 2.7 or later ... it may work
+ * on earlier stuff, but why chance it.
+ *
+ * We've since discovered that the gcc shipped with NeXT systems
+ * as "cc" is completely broken. It claims to be __GNUC__ and so
+ * on, but it doesn't implement half of the things that __GNUC__
+ * means. In particular it's missing inline and the __attribute__
+ * stuff. So we hack around it. PR#1613. -djg
+ */
+#if !defined(__GNUC__) || __GNUC__ < 2 || \
+ (__GNUC__ == 2 && __GNUC_MINOR__ < 7) ||\
+ defined(NEXT)
+#ifndef __attribute__
+#define __attribute__(__x)
+#endif
+#define APR_INLINE
+#define APR_HAS_INLINE 0
+#else
+#define APR_INLINE __inline__
+#define APR_HAS_INLINE 1
+#endif
+
+#define APR_HAVE_ARPA_INET_H 1
+#define APR_HAVE_CONIO_H 0
+#define APR_HAVE_CRYPT_H 1
+#define APR_HAVE_CTYPE_H 1
+#define APR_HAVE_DIRENT_H 1
+#define APR_HAVE_ERRNO_H 1
+#define APR_HAVE_FCNTL_H 1
+#define APR_HAVE_IO_H 0
+#define APR_HAVE_LIMITS_H 1
+#define APR_HAVE_NETDB_H 1
+#define APR_HAVE_NETINET_IN_H 1
+#define APR_HAVE_NETINET_SCTP_H 0
+#define APR_HAVE_NETINET_SCTP_UIO_H 0
+#define APR_HAVE_NETINET_TCP_H 1
+#define APR_HAVE_PTHREAD_H 1
+#define APR_HAVE_SEMAPHORE_H 1
+#define APR_HAVE_SIGNAL_H 1
+#define APR_HAVE_STDARG_H 1
+#define APR_HAVE_STDINT_H 1
+#define APR_HAVE_STDIO_H 1
+#define APR_HAVE_STDLIB_H 1
+#define APR_HAVE_STRING_H 1
+#define APR_HAVE_STRINGS_H 1
+#define APR_HAVE_SYS_IOCTL_H 1
+#define APR_HAVE_SYS_SENDFILE_H 1
+#define APR_HAVE_SYS_SIGNAL_H 1
+#define APR_HAVE_SYS_SOCKET_H 1
+#define APR_HAVE_SYS_SOCKIO_H 0
+#define APR_HAVE_SYS_SYSLIMITS_H 0
+#define APR_HAVE_SYS_TIME_H 1
+#define APR_HAVE_SYS_TYPES_H 1
+#define APR_HAVE_SYS_UIO_H 1
+#define APR_HAVE_SYS_UN_H 1
+#define APR_HAVE_SYS_WAIT_H 1
+#define APR_HAVE_TIME_H 1
+#define APR_HAVE_UNISTD_H 1
+#define APR_HAVE_WINDOWS_H 0
+#define APR_HAVE_WINSOCK2_H 0
+
+/** @} */
+/** @} */
+
+/* We don't include our conditional headers within the doxyblocks
+ * or the extern "C" namespace
+ */
+
+#if APR_HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#if APR_HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#if defined(__cplusplus) && !defined(__STDC_CONSTANT_MACROS)
+/* C99 7.18.4 requires that stdint.h only exposes INT64_C
+ * and UINT64_C for C++ implementations if this is defined: */
+#define __STDC_CONSTANT_MACROS
+#endif
+
+#if APR_HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#if APR_HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef OS2
+#define INCL_DOS
+#define INCL_DOSERRORS
+#include <os2.h>
+#endif
+
+/* header files for PATH_MAX, _POSIX_PATH_MAX */
+#if APR_HAVE_LIMITS_H
+#include <limits.h>
+#else
+#if APR_HAVE_SYS_SYSLIMITS_H
+#include <sys/syslimits.h>
+#endif
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup apr_platform
+ * @ingroup APR
+ * @{
+ */
+
+#define APR_HAVE_SHMEM_MMAP_TMP 1
+#define APR_HAVE_SHMEM_MMAP_SHM 1
+#define APR_HAVE_SHMEM_MMAP_ZERO 1
+#define APR_HAVE_SHMEM_SHMGET_ANON 1
+#define APR_HAVE_SHMEM_SHMGET 1
+#define APR_HAVE_SHMEM_MMAP_ANON 1
+#define APR_HAVE_SHMEM_BEOS 0
+
+#define APR_USE_SHMEM_MMAP_TMP 0
+#define APR_USE_SHMEM_MMAP_SHM 0
+#define APR_USE_SHMEM_MMAP_ZERO 0
+#define APR_USE_SHMEM_SHMGET_ANON 0
+#define APR_USE_SHMEM_SHMGET 1
+#define APR_USE_SHMEM_MMAP_ANON 1
+#define APR_USE_SHMEM_BEOS 0
+
+#define APR_USE_FLOCK_SERIALIZE 0
+#define APR_USE_SYSVSEM_SERIALIZE 1
+#define APR_USE_POSIXSEM_SERIALIZE 0
+#define APR_USE_FCNTL_SERIALIZE 0
+#define APR_USE_PROC_PTHREAD_SERIALIZE 0
+#define APR_USE_PTHREAD_SERIALIZE 1
+
+#define APR_HAS_FLOCK_SERIALIZE 1
+#define APR_HAS_SYSVSEM_SERIALIZE 1
+#define APR_HAS_POSIXSEM_SERIALIZE 1
+#define APR_HAS_FCNTL_SERIALIZE 1
+#define APR_HAS_PROC_PTHREAD_SERIALIZE 1
+
+#define APR_PROCESS_LOCK_IS_GLOBAL 0
+
+#define APR_HAVE_CORKABLE_TCP 1
+#define APR_HAVE_GETRLIMIT 1
+#define APR_HAVE_IN_ADDR 1
+#define APR_HAVE_INET_ADDR 1
+#define APR_HAVE_INET_NETWORK 1
+#define APR_HAVE_IPV6 1
+#define APR_HAVE_MEMMOVE 1
+#define APR_HAVE_SETRLIMIT 1
+#define APR_HAVE_SIGACTION 1
+#define APR_HAVE_SIGSUSPEND 1
+#define APR_HAVE_SIGWAIT 1
+#define APR_HAVE_SA_STORAGE 1
+#define APR_HAVE_STRCASECMP 1
+#define APR_HAVE_STRDUP 1
+#define APR_HAVE_STRICMP 0
+#define APR_HAVE_STRNCASECMP 1
+#define APR_HAVE_STRNICMP 0
+#define APR_HAVE_STRSTR 1
+#define APR_HAVE_MEMCHR 1
+#define APR_HAVE_STRUCT_RLIMIT 1
+#define APR_HAVE_UNION_SEMUN 0
+#define APR_HAVE_SCTP 0
+#define APR_HAVE_IOVEC 1
+
+/* APR Feature Macros */
+#define APR_HAS_SHARED_MEMORY 1
+#define APR_HAS_THREADS 1
+#define APR_HAS_SENDFILE 1
+#define APR_HAS_MMAP 1
+#define APR_HAS_FORK 1
+#define APR_HAS_RANDOM 1
+#define APR_HAS_OTHER_CHILD 1
+#define APR_HAS_DSO 1
+#define APR_HAS_SO_ACCEPTFILTER 0
+#define APR_HAS_UNICODE_FS 0
+#define APR_HAS_PROC_INVOKED 0
+#define APR_HAS_USER 1
+#define APR_HAS_LARGE_FILES 0
+#define APR_HAS_XTHREAD_FILES 0
+#define APR_HAS_OS_UUID 0
+
+#define APR_PROCATTR_USER_SET_REQUIRES_PASSWORD 0
+
+/* APR sets APR_FILES_AS_SOCKETS to 1 on systems where it is possible
+ * to poll on files/pipes.
+ */
+#define APR_FILES_AS_SOCKETS 1
+
+/* This macro indicates whether or not EBCDIC is the native character set.
+ */
+#define APR_CHARSET_EBCDIC 0
+
+/* If we have a TCP implementation that can be "corked", what flag
+ * do we use?
+ */
+#define APR_TCP_NOPUSH_FLAG TCP_CORK
+
+/* Is the TCP_NODELAY socket option inherited from listening sockets?
+*/
+#define APR_TCP_NODELAY_INHERITED 1
+
+/* Is the O_NONBLOCK flag inherited from listening sockets?
+*/
+#define APR_O_NONBLOCK_INHERITED 0
+
+/* Typedefs that APR needs. */
+
+typedef unsigned char apr_byte_t;
+
+typedef short apr_int16_t;
+typedef unsigned short apr_uint16_t;
+
+typedef int apr_int32_t;
+typedef unsigned int apr_uint32_t;
+
+typedef long apr_int64_t;
+typedef unsigned long apr_uint64_t;
+
+typedef size_t apr_size_t;
+typedef ssize_t apr_ssize_t;
+typedef off_t apr_off_t;
+typedef socklen_t apr_socklen_t;
+typedef ino_t apr_ino_t;
+
+#define APR_SIZEOF_VOIDP 8
+
+#if APR_SIZEOF_VOIDP == 8
+typedef apr_uint64_t apr_uintptr_t;
+#else
+typedef apr_uint32_t apr_uintptr_t;
+#endif
+
+/* Are we big endian? */
+#define APR_IS_BIGENDIAN 0
+
+/* Mechanisms to properly type numeric literals */
+#define APR_INT64_C(val) INT64_C(val)
+#define APR_UINT64_C(val) UINT64_C(val)
+
+#ifdef INT16_MIN
+#define APR_INT16_MIN INT16_MIN
+#else
+#define APR_INT16_MIN (-0x7fff - 1)
+#endif
+
+#ifdef INT16_MAX
+#define APR_INT16_MAX INT16_MAX
+#else
+#define APR_INT16_MAX (0x7fff)
+#endif
+
+#ifdef UINT16_MAX
+#define APR_UINT16_MAX UINT16_MAX
+#else
+#define APR_UINT16_MAX (0xffff)
+#endif
+
+#ifdef INT32_MIN
+#define APR_INT32_MIN INT32_MIN
+#else
+#define APR_INT32_MIN (-0x7fffffff - 1)
+#endif
+
+#ifdef INT32_MAX
+#define APR_INT32_MAX INT32_MAX
+#else
+#define APR_INT32_MAX 0x7fffffff
+#endif
+
+#ifdef UINT32_MAX
+#define APR_UINT32_MAX UINT32_MAX
+#else
+#define APR_UINT32_MAX (0xffffffffU)
+#endif
+
+#ifdef INT64_MIN
+#define APR_INT64_MIN INT64_MIN
+#else
+#define APR_INT64_MIN (APR_INT64_C(-0x7fffffffffffffff) - 1)
+#endif
+
+#ifdef INT64_MAX
+#define APR_INT64_MAX INT64_MAX
+#else
+#define APR_INT64_MAX APR_INT64_C(0x7fffffffffffffff)
+#endif
+
+#ifdef UINT64_MAX
+#define APR_UINT64_MAX UINT64_MAX
+#else
+#define APR_UINT64_MAX APR_UINT64_C(0xffffffffffffffff)
+#endif
+
+#define APR_SIZE_MAX (~((apr_size_t)0))
+
+
+/* Definitions that APR programs need to work properly. */
+
+/**
+ * APR public API wrap for C++ compilers.
+ */
+#ifdef __cplusplus
+#define APR_BEGIN_DECLS extern "C" {
+#define APR_END_DECLS }
+#else
+#define APR_BEGIN_DECLS
+#define APR_END_DECLS
+#endif
+
+/**
+ * Thread callbacks from APR functions must be declared with APR_THREAD_FUNC,
+ * so that they follow the platform's calling convention.
+ * <PRE>
+ *
+ * void* APR_THREAD_FUNC my_thread_entry_fn(apr_thread_t *thd, void *data);
+ *
+ * </PRE>
+ */
+#define APR_THREAD_FUNC
+
+/**
+ * The public APR functions are declared with APR_DECLARE(), so they may
+ * use the most appropriate calling convention. Public APR functions with
+ * variable arguments must use APR_DECLARE_NONSTD().
+ *
+ * @remark Both the declaration and implementations must use the same macro.
+ *
+ * <PRE>
+ * APR_DECLARE(rettype) apr_func(args)
+ * </PRE>
+ * @see APR_DECLARE_NONSTD @see APR_DECLARE_DATA
+ * @remark Note that when APR compiles the library itself, it passes the
+ * symbol -DAPR_DECLARE_EXPORT to the compiler on some platforms (e.g. Win32)
+ * to export public symbols from the dynamic library build.\n
+ * The user must define the APR_DECLARE_STATIC when compiling to target
+ * the static APR library on some platforms (e.g. Win32.) The public symbols
+ * are neither exported nor imported when APR_DECLARE_STATIC is defined.\n
+ * By default, compiling an application and including the APR public
+ * headers, without defining APR_DECLARE_STATIC, will prepare the code to be
+ * linked to the dynamic library.
+ */
+#define APR_DECLARE(type) type
+
+/**
+ * The public APR functions using variable arguments are declared with
+ * APR_DECLARE_NONSTD(), as they must follow the C language calling convention.
+ * @see APR_DECLARE @see APR_DECLARE_DATA
+ * @remark Both the declaration and implementations must use the same macro.
+ * <PRE>
+ *
+ * APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ *
+ * </PRE>
+ */
+#define APR_DECLARE_NONSTD(type) type
+
+/**
+ * The public APR variables are declared with AP_MODULE_DECLARE_DATA.
+ * This assures the appropriate indirection is invoked at compile time.
+ * @see APR_DECLARE @see APR_DECLARE_NONSTD
+ * @remark Note that the declaration and implementations use different forms,
+ * but both must include the macro.
+ *
+ * <PRE>
+ *
+ * extern APR_DECLARE_DATA type apr_variable;\n
+ * APR_DECLARE_DATA type apr_variable = value;
+ *
+ * </PRE>
+ */
+#define APR_DECLARE_DATA
+
+/* Define APR_SSIZE_T_FMT.
+ * If ssize_t is an integer we define it to be "d",
+ * if ssize_t is a long int we define it to be "ld",
+ * if ssize_t is neither we declare an error here.
+ * I looked for a better way to define this here, but couldn't find one, so
+ * to find the logic for this definition search for "ssize_t_fmt" in
+ * configure.in.
+ */
+#define APR_SSIZE_T_FMT "ld"
+
+/* And APR_SIZE_T_FMT */
+#define APR_SIZE_T_FMT "lu"
+
+/* And APR_OFF_T_FMT */
+#define APR_OFF_T_FMT "ld"
+
+/* And APR_PID_T_FMT */
+#define APR_PID_T_FMT "d"
+
+/* And APR_INT64_T_FMT */
+#define APR_INT64_T_FMT "ld"
+
+/* And APR_UINT64_T_FMT */
+#define APR_UINT64_T_FMT "lu"
+
+/* And APR_UINT64_T_HEX_FMT */
+#define APR_UINT64_T_HEX_FMT "lx"
+
+/* Does the proc mutex lock threads too */
+#define APR_PROC_MUTEX_IS_GLOBAL 0
+
+/* Local machine definition for console and log output. */
+#define APR_EOL_STR "\n"
+
+#if APR_HAVE_SYS_WAIT_H
+#ifdef WEXITSTATUS
+#define apr_wait_t int
+#else
+#define apr_wait_t union wait
+#define WEXITSTATUS(status) (int)((status).w_retcode)
+#define WTERMSIG(status) (int)((status).w_termsig)
+#endif /* !WEXITSTATUS */
+#elif defined(__MINGW32__)
+typedef int apr_wait_t;
+#endif /* HAVE_SYS_WAIT_H */
+
+#if defined(PATH_MAX)
+#define APR_PATH_MAX PATH_MAX
+#elif defined(_POSIX_PATH_MAX)
+#define APR_PATH_MAX _POSIX_PATH_MAX
+#else
+#error no decision has been made on APR_PATH_MAX for your platform
+#endif
+
+#define APR_DSOPATH "LD_LIBRARY_PATH"
+
+/** @} */
+
+/* Definitions that only Win32 programs need to compile properly. */
+
+/* XXX These simply don't belong here, perhaps in apr_portable.h
+ * based on some APR_HAVE_PID/GID/UID?
+ */
+#ifdef __MINGW32__
+#ifndef __GNUC__
+typedef int pid_t;
+#endif
+typedef int uid_t;
+typedef int gid_t;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APR_H */
--- /dev/null
+/* include/arch/unix/apr_private.h. Generated from apr_private.h.in by configure. */
+/* include/arch/unix/apr_private.h.in. Generated from configure.in by autoheader. */
+
+
+#ifndef APR_PRIVATE_H
+#define APR_PRIVATE_H
+
+
+/* Define as function which can be used for conversion of strings to
+ apr_int64_t */
+#define APR_INT64_STRFN strtol
+
+/* Define as function used for conversion of strings to apr_off_t */
+#define APR_OFF_T_STRFN strtol
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to path of random device */
+#define DEV_RANDOM "/dev/urandom"
+
+/* Define if struct dirent has an inode member */
+#define DIRENT_INODE d_fileno
+
+/* Define if struct dirent has a d_type member */
+#define DIRENT_TYPE d_type
+
+/* Define if DSO support uses dlfcn.h */
+#define DSO_USE_DLFCN 1
+
+/* Define if DSO support uses dyld.h */
+/* #undef DSO_USE_DYLD */
+
+/* Define if DSO support uses shl_load */
+/* #undef DSO_USE_SHL */
+
+/* Define to list of paths to EGD sockets */
+/* #undef EGD_DEFAULT_SOCKET */
+
+/* Define if fcntl locks affect threads within the process */
+/* #undef FCNTL_IS_GLOBAL */
+
+/* Define if fcntl returns EACCES when F_SETLK is already held */
+/* #undef FCNTL_TRYACQUIRE_EACCES */
+
+/* Define if flock locks affect threads within the process */
+/* #undef FLOCK_IS_GLOBAL */
+
+/* Define if gethostbyaddr is thread safe */
+/* #undef GETHOSTBYADDR_IS_THREAD_SAFE */
+
+/* Define if gethostbyname is thread safe */
+/* #undef GETHOSTBYNAME_IS_THREAD_SAFE */
+
+/* Define if gethostbyname_r has the glibc style */
+#define GETHOSTBYNAME_R_GLIBC2 1
+
+/* Define if gethostbyname_r has the hostent_data for the third argument */
+/* #undef GETHOSTBYNAME_R_HOSTENT_DATA */
+
+/* Define if getservbyname is thread safe */
+/* #undef GETSERVBYNAME_IS_THREAD_SAFE */
+
+/* Define if getservbyname_r has the glibc style */
+#define GETSERVBYNAME_R_GLIBC2 1
+
+/* Define if getservbyname_r has the OSF/1 style */
+/* #undef GETSERVBYNAME_R_OSF1 */
+
+/* Define if getservbyname_r has the Solaris style */
+/* #undef GETSERVBYNAME_R_SOLARIS */
+
+/* Define if accept4 function is supported */
+/* #undef HAVE_ACCEPT4 */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#define HAVE_ARPA_INET_H 1
+
+/* Define if compiler provides atomic builtins */
+#define HAVE_ATOMIC_BUILTINS 1
+
+/* Define if BONE_VERSION is defined in sys/socket.h */
+/* #undef HAVE_BONE_VERSION */
+
+/* Define to 1 if you have the <ByteOrder.h> header file. */
+/* #undef HAVE_BYTEORDER_H */
+
+/* Define to 1 if you have the `calloc' function. */
+#define HAVE_CALLOC 1
+
+/* Define to 1 if you have the <conio.h> header file. */
+/* #undef HAVE_CONIO_H */
+
+/* Define to 1 if you have the `create_area' function. */
+/* #undef HAVE_CREATE_AREA */
+
+/* Define to 1 if you have the `create_sem' function. */
+/* #undef HAVE_CREATE_SEM */
+
+/* Define to 1 if you have the <crypt.h> header file. */
+#define HAVE_CRYPT_H 1
+
+/* Define to 1 if you have the <ctype.h> header file. */
+#define HAVE_CTYPE_H 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dir.h> header file. */
+/* #undef HAVE_DIR_H */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <dl.h> header file. */
+/* #undef HAVE_DL_H */
+
+/* Define if dup3 function is supported */
+/* #undef HAVE_DUP3 */
+
+/* Define if EGD is supported */
+/* #undef HAVE_EGD */
+
+/* Define if the epoll interface is supported */
+#define HAVE_EPOLL 1
+
+/* Define if epoll_create1 function is supported */
+/* #undef HAVE_EPOLL_CREATE1 */
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdatasync' function. */
+#define HAVE_FDATASYNC 1
+
+/* Define to 1 if you have the `flock' function. */
+#define HAVE_FLOCK 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define if F_SETLK is defined in fcntl.h */
+#define HAVE_F_SETLK 1
+
+/* Define if getaddrinfo accepts the AI_ADDRCONFIG flag */
+#define HAVE_GAI_ADDRCONFIG 1
+
+/* Define to 1 if you have the `gai_strerror' function. */
+#define HAVE_GAI_STRERROR 1
+
+/* Define if getaddrinfo exists and works well enough for APR */
+#define HAVE_GETADDRINFO 1
+
+/* Define to 1 if you have the `getenv' function. */
+#define HAVE_GETENV 1
+
+/* Define to 1 if you have the `getgrgid_r' function. */
+#define HAVE_GETGRGID_R 1
+
+/* Define to 1 if you have the `getgrnam_r' function. */
+#define HAVE_GETGRNAM_R 1
+
+/* Define to 1 if you have the `gethostbyaddr_r' function. */
+#define HAVE_GETHOSTBYADDR_R 1
+
+/* Define to 1 if you have the `gethostbyname_r' function. */
+#define HAVE_GETHOSTBYNAME_R 1
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define HAVE_GETIFADDRS 1
+
+/* Define if getnameinfo exists */
+#define HAVE_GETNAMEINFO 1
+
+/* Define to 1 if you have the `getpass' function. */
+#define HAVE_GETPASS 1
+
+/* Define to 1 if you have the `getpassphrase' function. */
+/* #undef HAVE_GETPASSPHRASE */
+
+/* Define to 1 if you have the `getpwnam_r' function. */
+#define HAVE_GETPWNAM_R 1
+
+/* Define to 1 if you have the `getpwuid_r' function. */
+#define HAVE_GETPWUID_R 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have the `getservbyname_r' function. */
+#define HAVE_GETSERVBYNAME_R 1
+
+/* Define to 1 if you have the `gmtime_r' function. */
+#define HAVE_GMTIME_R 1
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define if hstrerror is present */
+/* #undef HAVE_HSTRERROR */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <io.h> header file. */
+/* #undef HAVE_IO_H */
+
+/* Define to 1 if you have the `isinf' function. */
+#define HAVE_ISINF 1
+
+/* Define to 1 if you have the `isnan' function. */
+#define HAVE_ISNAN 1
+
+/* Define to 1 if you have the <kernel/OS.h> header file. */
+/* #undef HAVE_KERNEL_OS_H */
+
+/* Define to 1 if you have the `kqueue' function. */
+/* #undef HAVE_KQUEUE */
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBADVAPI32 */
+
+/* Define to 1 if you have the `bsd' library (-lbsd). */
+/* #undef HAVE_LIBBSD */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBKERNEL32 */
+
+/* Define to 1 if you have the `msvcrt' library (-lmsvcrt). */
+/* #undef HAVE_LIBMSVCRT */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBRPCRT4 */
+
+/* Define to 1 if you have the `sendfile' library (-lsendfile). */
+/* #undef HAVE_LIBSENDFILE */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBSHELL32 */
+
+/* Define to 1 if you have the `truerand' library (-ltruerand). */
+/* #undef HAVE_LIBTRUERAND */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBWS2_32 */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the `localtime_r' function. */
+#define HAVE_LOCALTIME_R 1
+
+/* Define if LOCK_EX is defined in sys/file.h */
+#define HAVE_LOCK_EX 1
+
+/* Define to 1 if you have the <mach-o/dyld.h> header file. */
+/* #undef HAVE_MACH_O_DYLD_H */
+
+/* Define to 1 if you have the <malloc.h> header file. */
+#define HAVE_MALLOC_H 1
+
+/* Define if MAP_ANON is defined in sys/mman.h */
+#define HAVE_MAP_ANON 1
+
+/* Define to 1 if you have the `memchr' function. */
+#define HAVE_MEMCHR 1
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mkstemp64' function. */
+/* #undef HAVE_MKSTEMP64 */
+
+/* Define to 1 if you have the `mmap' function. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the `mmap64' function. */
+/* #undef HAVE_MMAP64 */
+
+/* Define to 1 if you have the `munmap' function. */
+#define HAVE_MUNMAP 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <netinet/sctp.h> header file. */
+/* #undef HAVE_NETINET_SCTP_H */
+
+/* Define to 1 if you have the <netinet/sctp_uio.h> header file. */
+/* #undef HAVE_NETINET_SCTP_UIO_H */
+
+/* Defined if netinet/tcp.h is present */
+#define HAVE_NETINET_TCP_H 1
+
+/* Define to 1 if you have the <net/errno.h> header file. */
+/* #undef HAVE_NET_ERRNO_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#define HAVE_NL_LANGINFO 1
+
+/* Define to 1 if you have the <os2.h> header file. */
+/* #undef HAVE_OS2_H */
+
+/* Define to 1 if you have the <osreldate.h> header file. */
+/* #undef HAVE_OSRELDATE_H */
+
+/* Define to 1 if you have the <OS.h> header file. */
+/* #undef HAVE_OS_H */
+
+/* Define to 1 if you have the `poll' function. */
+#define HAVE_POLL 1
+
+/* Define if POLLIN is defined */
+#define HAVE_POLLIN 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#define HAVE_POLL_H 1
+
+/* Define to 1 if you have the `port_create' function. */
+/* #undef HAVE_PORT_CREATE */
+
+/* Define to 1 if you have the <process.h> header file. */
+/* #undef HAVE_PROCESS_H */
+
+/* Define to 1 if you have the `pthread_attr_setguardsize' function. */
+#define HAVE_PTHREAD_ATTR_SETGUARDSIZE 1
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* Define to 1 if you have the `pthread_key_delete' function. */
+#define HAVE_PTHREAD_KEY_DELETE 1
+
+/* Define to 1 if you have the `pthread_mutexattr_setpshared' function. */
+#define HAVE_PTHREAD_MUTEXATTR_SETPSHARED 1
+
+/* Define if recursive pthread mutexes are available */
+#define HAVE_PTHREAD_MUTEX_RECURSIVE 1
+
+/* Define if cross-process robust mutexes are available */
+#define HAVE_PTHREAD_MUTEX_ROBUST 1
+
+/* Define if PTHREAD_PROCESS_SHARED is defined in pthread.h */
+#define HAVE_PTHREAD_PROCESS_SHARED 1
+
+/* Define if pthread rwlocks are available */
+#define HAVE_PTHREAD_RWLOCKS 1
+
+/* Define to 1 if you have the `pthread_rwlock_init' function. */
+#define HAVE_PTHREAD_RWLOCK_INIT 1
+
+/* Define to 1 if you have the `pthread_yield' function. */
+#define HAVE_PTHREAD_YIELD 1
+
+/* Define to 1 if you have the `putenv' function. */
+#define HAVE_PUTENV 1
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `readdir64_r' function. */
+/* #undef HAVE_READDIR64_R */
+
+/* Define to 1 if you have the <sched.h> header file. */
+/* #undef HAVE_SCHED_H */
+
+/* Define to 1 if you have the `sched_yield' function. */
+/* #undef HAVE_SCHED_YIELD */
+
+/* Define to 1 if you have the <semaphore.h> header file. */
+#define HAVE_SEMAPHORE_H 1
+
+/* Define to 1 if you have the `semctl' function. */
+#define HAVE_SEMCTL 1
+
+/* Define to 1 if you have the `semget' function. */
+#define HAVE_SEMGET 1
+
+/* Define to 1 if you have the `sem_close' function. */
+#define HAVE_SEM_CLOSE 1
+
+/* Define to 1 if you have the `sem_post' function. */
+#define HAVE_SEM_POST 1
+
+/* Define if SEM_UNDO is defined in sys/sem.h */
+#define HAVE_SEM_UNDO 1
+
+/* Define to 1 if you have the `sem_unlink' function. */
+#define HAVE_SEM_UNLINK 1
+
+/* Define to 1 if you have the `sem_wait' function. */
+#define HAVE_SEM_WAIT 1
+
+/* Define to 1 if you have the `sendfile' function. */
+#define HAVE_SENDFILE 1
+
+/* Define to 1 if you have the `sendfile64' function. */
+/* #undef HAVE_SENDFILE64 */
+
+/* Define to 1 if you have the `sendfilev' function. */
+/* #undef HAVE_SENDFILEV */
+
+/* Define to 1 if you have the `sendfilev64' function. */
+/* #undef HAVE_SENDFILEV64 */
+
+/* Define to 1 if you have the `send_file' function. */
+/* #undef HAVE_SEND_FILE */
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define to 1 if you have the `set_h_errno' function. */
+/* #undef HAVE_SET_H_ERRNO */
+
+/* Define to 1 if you have the `shmat' function. */
+#define HAVE_SHMAT 1
+
+/* Define to 1 if you have the `shmctl' function. */
+#define HAVE_SHMCTL 1
+
+/* Define to 1 if you have the `shmdt' function. */
+#define HAVE_SHMDT 1
+
+/* Define to 1 if you have the `shmget' function. */
+#define HAVE_SHMGET 1
+
+/* Define to 1 if you have the `shm_open' function. */
+#define HAVE_SHM_OPEN 1
+
+/* Define to 1 if you have the `shm_unlink' function. */
+#define HAVE_SHM_UNLINK 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#define HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the `sigsuspend' function. */
+#define HAVE_SIGSUSPEND 1
+
+/* Define to 1 if you have the `sigwait' function. */
+#define HAVE_SIGWAIT 1
+
+/* Whether you have socklen_t */
+#define HAVE_SOCKLEN_T 1
+
+/* Define if the SOCK_CLOEXEC flag is supported */
+/* #undef HAVE_SOCK_CLOEXEC */
+
+/* Define if SO_ACCEPTFILTER is defined in sys/socket.h */
+/* #undef HAVE_SO_ACCEPTFILTER */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#define HAVE_STDDEF_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror_r' function. */
+#define HAVE_STRERROR_R 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strstr' function. */
+#define HAVE_STRSTR 1
+
+/* Define if struct impreq was found */
+#define HAVE_STRUCT_IPMREQ 1
+
+/* Define to 1 if `st_atimensec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIMENSEC */
+
+/* Define to 1 if `st_atime_n' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIME_N */
+
+/* Define to 1 if `st_atim.tv_nsec' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC 1
+
+/* Define to 1 if `st_blocks' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_BLOCKS 1
+
+/* Define to 1 if `st_ctimensec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIMENSEC */
+
+/* Define to 1 if `st_ctime_n' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIME_N */
+
+/* Define to 1 if `st_ctim.tv_nsec' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC 1
+
+/* Define to 1 if `st_mtimensec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIMENSEC */
+
+/* Define to 1 if `st_mtime_n' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIME_N */
+
+/* Define to 1 if `st_mtim.tv_nsec' is member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1
+
+/* Define to 1 if `tm_gmtoff' is member of `struct tm'. */
+#define HAVE_STRUCT_TM_TM_GMTOFF 1
+
+/* Define to 1 if `__tm_gmtoff' is member of `struct tm'. */
+/* #undef HAVE_STRUCT_TM___TM_GMTOFF */
+
+/* Define to 1 if you have the <sysapi.h> header file. */
+/* #undef HAVE_SYSAPI_H */
+
+/* Define to 1 if you have the <sysgtime.h> header file. */
+/* #undef HAVE_SYSGTIME_H */
+
+/* Define to 1 if you have the <sys/file.h> header file. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/mutex.h> header file. */
+/* #undef HAVE_SYS_MUTEX_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+#define HAVE_SYS_POLL_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/sem.h> header file. */
+#define HAVE_SYS_SEM_H 1
+
+/* Define to 1 if you have the <sys/sendfile.h> header file. */
+#define HAVE_SYS_SENDFILE_H 1
+
+/* Define to 1 if you have the <sys/shm.h> header file. */
+#define HAVE_SYS_SHM_H 1
+
+/* Define to 1 if you have the <sys/signal.h> header file. */
+#define HAVE_SYS_SIGNAL_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/sockio.h> header file. */
+/* #undef HAVE_SYS_SOCKIO_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysctl.h> header file. */
+#define HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the <sys/syslimits.h> header file. */
+/* #undef HAVE_SYS_SYSLIMITS_H */
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+#define HAVE_SYS_UIO_H 1
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#define HAVE_SYS_UN_H 1
+
+/* Define to 1 if you have the <sys/uuid.h> header file. */
+/* #undef HAVE_SYS_UUID_H */
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define if TCP_CORK is defined in netinet/tcp.h */
+#define HAVE_TCP_CORK 1
+
+/* Define if TCP_NODELAY and TCP_CORK can be enabled at the same time */
+#define HAVE_TCP_NODELAY_WITH_CORK 1
+
+/* Define if TCP_NOPUSH is defined in netinet/tcp.h */
+/* #undef HAVE_TCP_NOPUSH */
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the <tpfeq.h> header file. */
+/* #undef HAVE_TPFEQ_H */
+
+/* Define to 1 if you have the <tpfio.h> header file. */
+/* #undef HAVE_TPFIO_H */
+
+/* Define if truerand is supported */
+/* #undef HAVE_TRUERAND */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <unix.h> header file. */
+/* #undef HAVE_UNIX_H */
+
+/* Define to 1 if you have the `unsetenv' function. */
+#define HAVE_UNSETENV 1
+
+/* Define to 1 if you have the `utime' function. */
+#define HAVE_UTIME 1
+
+/* Define to 1 if you have the `utimes' function. */
+#define HAVE_UTIMES 1
+
+/* Define to 1 if you have the `uuid_create' function. */
+/* #undef HAVE_UUID_CREATE */
+
+/* Define to 1 if you have the `uuid_generate' function. */
+#define HAVE_UUID_GENERATE 1
+
+/* Define to 1 if you have the <uuid.h> header file. */
+/* #undef HAVE_UUID_H */
+
+/* Define to 1 if you have the <uuid/uuid.h> header file. */
+/* #undef HAVE_UUID_UUID_H */
+
+/* Define if C compiler supports VLA */
+#define HAVE_VLA 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if you have the <windows.h> header file. */
+/* #undef HAVE_WINDOWS_H */
+
+/* Define to 1 if you have the <winsock2.h> header file. */
+/* #undef HAVE_WINSOCK2_H */
+
+/* Define to 1 if you have the `writev' function. */
+#define HAVE_WRITEV 1
+
+/* Define for z/OS pthread API nuances */
+/* #undef HAVE_ZOS_PTHREADS */
+
+/* Define if EAI_ error codes from getaddrinfo are negative */
+#define NEGATIVE_EAI 1
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define if POSIX semaphores affect threads within the process */
+/* #undef POSIXSEM_IS_GLOBAL */
+
+/* Define on PowerPC 405 where errata 77 applies */
+/* #undef PPC405_ERRATA */
+
+/* Define if pthread_attr_getdetachstate() has one arg */
+/* #undef PTHREAD_ATTR_GETDETACHSTATE_TAKES_ONE_ARG */
+
+/* Define if pthread_getspecific() has two args */
+/* #undef PTHREAD_GETSPECIFIC_TAKES_TWO_ARGS */
+
+/* Define if readdir is thread safe */
+/* #undef READDIR_IS_THREAD_SAFE */
+
+/* Define to 1 if the `setpgrp' function takes no argument. */
+#define SETPGRP_VOID 1
+
+/* */
+/* #undef SIGWAIT_TAKES_ONE_ARG */
+
+/* The size of `char', as computed by sizeof. */
+#define SIZEOF_CHAR 1
+
+/* The size of `int', as computed by sizeof. */
+#define SIZEOF_INT 4
+
+/* The size of `long', as computed by sizeof. */
+#define SIZEOF_LONG 8
+
+/* The size of `long long', as computed by sizeof. */
+#define SIZEOF_LONG_LONG 8
+
+/* The size of off_t */
+#define SIZEOF_OFF_T 8
+
+/* The size of pid_t */
+#define SIZEOF_PID_T 4
+
+/* The size of `short', as computed by sizeof. */
+#define SIZEOF_SHORT 2
+
+/* The size of size_t */
+#define SIZEOF_SIZE_T 8
+
+/* The size of ssize_t */
+#define SIZEOF_SSIZE_T 8
+
+/* The size of struct iovec */
+#define SIZEOF_STRUCT_IOVEC 16
+
+/* The size of `void*', as computed by sizeof. */
+#define SIZEOF_VOIDP 8
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if strerror returns int */
+/* #undef STRERROR_R_RC_INT */
+
+/* Define if SysV semaphores affect threads within the process */
+/* #undef SYSVSEM_IS_GLOBAL */
+
+/* Define if use of generic atomics is requested */
+/* #undef USE_ATOMICS_GENERIC */
+
+/* Define if BeOS Semaphores will be used */
+/* #undef USE_BEOSSEM */
+
+/* Define if SVR4-style fcntl() will be used */
+/* #undef USE_FCNTL_SERIALIZE */
+
+/* Define if 4.2BSD-style flock() will be used */
+/* #undef USE_FLOCK_SERIALIZE */
+
+/* Define if BeOS areas will be used */
+/* #undef USE_SHMEM_BEOS */
+
+/* Define if BeOS areas will be used */
+/* #undef USE_SHMEM_BEOS_ANON */
+
+/* Define if 4.4BSD-style mmap() via MAP_ANON will be used */
+#define USE_SHMEM_MMAP_ANON 1
+
+/* Define if mmap() via POSIX.1 shm_open() on temporary file will be used */
+/* #undef USE_SHMEM_MMAP_SHM */
+
+/* Define if Classical mmap() on temporary file will be used */
+/* #undef USE_SHMEM_MMAP_TMP */
+
+/* Define if SVR4-style mmap() on /dev/zero will be used */
+/* #undef USE_SHMEM_MMAP_ZERO */
+
+/* Define if OS/2 DosAllocSharedMem() will be used */
+/* #undef USE_SHMEM_OS2 */
+
+/* Define if OS/2 DosAllocSharedMem() will be used */
+/* #undef USE_SHMEM_OS2_ANON */
+
+/* Define if SysV IPC shmget() will be used */
+#define USE_SHMEM_SHMGET 1
+
+/* Define if SysV IPC shmget() will be used */
+/* #undef USE_SHMEM_SHMGET_ANON */
+
+/* Define if Windows shared memory will be used */
+/* #undef USE_SHMEM_WIN32 */
+
+/* Define if Windows CreateFileMapping() will be used */
+/* #undef USE_SHMEM_WIN32_ANON */
+
+/* Define if SysV IPC semget() will be used */
+#define USE_SYSVSEM_SERIALIZE 1
+
+/* Define if apr_wait_for_io_or_timeout() uses poll(2) */
+#define WAITIO_USES_POLL 1
+
+/* Define to 1 if your processor stores words with the most significant byte
+ first (like Motorola and SPARC, unlike Intel and VAX). */
+/* #undef WORDS_BIGENDIAN */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Enable extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef ssize_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+
+/* switch this on if we have a BeOS version below BONE */
+#if BEOS && !HAVE_BONE_VERSION
+#define BEOS_R5 1
+#else
+#define BEOS_BONE 1
+#endif
+
+/*
+ * Include common private declarations.
+ */
+#include "../apr_private_common.h"
+#endif /* APR_PRIVATE_H */
--- /dev/null
+/* 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.
+ */
+
+
+#ifndef APR_H
+#define APR_H
+
+/* GENERATED FILE WARNING! DO NOT EDIT apr.h
+ *
+ * You must modify apr.h.in instead.
+ *
+ * And please, make an effort to stub apr.hw and apr.hnw in the process.
+ */
+
+/**
+ * @file apr.h
+ * @brief APR Platform Definitions
+ * @remark This is a generated header generated from include/apr.h.in by
+ * ./configure, or copied from include/apr.hw or include/apr.hnw
+ * for Win32 or Netware by those build environments, respectively.
+ */
+
+/**
+ * @defgroup APR Apache Portability Runtime library
+ * @{
+ */
+/**
+ * @defgroup apr_platform Platform Definitions
+ * @{
+ * @warning
+ * <strong><em>The actual values of macros and typedefs on this page<br>
+ * are platform specific and should NOT be relied upon!</em></strong>
+ */
+
+/* So that we can use inline on some critical functions, and use
+ * GNUC attributes (such as to get -Wall warnings for printf-like
+ * functions). Only do this in gcc 2.7 or later ... it may work
+ * on earlier stuff, but why chance it.
+ *
+ * We've since discovered that the gcc shipped with NeXT systems
+ * as "cc" is completely broken. It claims to be __GNUC__ and so
+ * on, but it doesn't implement half of the things that __GNUC__
+ * means. In particular it's missing inline and the __attribute__
+ * stuff. So we hack around it. PR#1613. -djg
+ */
+#if !defined(__GNUC__) || __GNUC__ < 2 || \
+ (__GNUC__ == 2 && __GNUC_MINOR__ < 7) ||\
+ defined(NEXT)
+#ifndef __attribute__
+#define __attribute__(__x)
+#endif
+#define APR_INLINE
+#define APR_HAS_INLINE 0
+#else
+#define APR_INLINE __inline__
+#define APR_HAS_INLINE 1
+#endif
+
+#define APR_HAVE_ARPA_INET_H 1
+#define APR_HAVE_CONIO_H 0
+#define APR_HAVE_CRYPT_H 0
+#define APR_HAVE_CTYPE_H 1
+#define APR_HAVE_DIRENT_H 1
+#define APR_HAVE_ERRNO_H 1
+#define APR_HAVE_FCNTL_H 1
+#define APR_HAVE_IO_H 0
+#define APR_HAVE_LIMITS_H 1
+#define APR_HAVE_NETDB_H 1
+#define APR_HAVE_NETINET_IN_H 1
+#define APR_HAVE_NETINET_SCTP_H 0
+#define APR_HAVE_NETINET_SCTP_UIO_H 0
+#define APR_HAVE_NETINET_TCP_H 1
+#define APR_HAVE_PTHREAD_H 1
+#define APR_HAVE_SEMAPHORE_H 1
+#define APR_HAVE_SIGNAL_H 1
+#define APR_HAVE_STDARG_H 1
+#define APR_HAVE_STDINT_H 1
+#define APR_HAVE_STDIO_H 1
+#define APR_HAVE_STDLIB_H 1
+#define APR_HAVE_STRING_H 1
+#define APR_HAVE_STRINGS_H 1
+#define APR_HAVE_SYS_IOCTL_H 1
+#define APR_HAVE_SYS_SENDFILE_H 0
+#define APR_HAVE_SYS_SIGNAL_H 1
+#define APR_HAVE_SYS_SOCKET_H 1
+#define APR_HAVE_SYS_SOCKIO_H 1
+#define APR_HAVE_SYS_SYSLIMITS_H 1
+#define APR_HAVE_SYS_TIME_H 1
+#define APR_HAVE_SYS_TYPES_H 1
+#define APR_HAVE_SYS_UIO_H 1
+#define APR_HAVE_SYS_UN_H 1
+#define APR_HAVE_SYS_WAIT_H 1
+#define APR_HAVE_TIME_H 1
+#define APR_HAVE_UNISTD_H 1
+#define APR_HAVE_WINDOWS_H 0
+#define APR_HAVE_WINSOCK2_H 0
+
+/** @} */
+/** @} */
+
+/* We don't include our conditional headers within the doxyblocks
+ * or the extern "C" namespace
+ */
+
+#if APR_HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#if APR_HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#if defined(__cplusplus) && !defined(__STDC_CONSTANT_MACROS)
+/* C99 7.18.4 requires that stdint.h only exposes INT64_C
+ * and UINT64_C for C++ implementations if this is defined: */
+#define __STDC_CONSTANT_MACROS
+#endif
+
+#if APR_HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#if APR_HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef OS2
+#define INCL_DOS
+#define INCL_DOSERRORS
+#include <os2.h>
+#endif
+
+/* header files for PATH_MAX, _POSIX_PATH_MAX */
+#if APR_HAVE_LIMITS_H
+#include <limits.h>
+#else
+#if APR_HAVE_SYS_SYSLIMITS_H
+#include <sys/syslimits.h>
+#endif
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup apr_platform
+ * @ingroup APR
+ * @{
+ */
+
+#define APR_HAVE_SHMEM_MMAP_TMP 1
+#define APR_HAVE_SHMEM_MMAP_SHM 1
+#define APR_HAVE_SHMEM_MMAP_ZERO 0
+#define APR_HAVE_SHMEM_SHMGET_ANON 1
+#define APR_HAVE_SHMEM_SHMGET 1
+#define APR_HAVE_SHMEM_MMAP_ANON 1
+#define APR_HAVE_SHMEM_BEOS 0
+
+#define APR_USE_SHMEM_MMAP_TMP 0
+#define APR_USE_SHMEM_MMAP_SHM 0
+#define APR_USE_SHMEM_MMAP_ZERO 0
+#define APR_USE_SHMEM_SHMGET_ANON 0
+#define APR_USE_SHMEM_SHMGET 1
+#define APR_USE_SHMEM_MMAP_ANON 1
+#define APR_USE_SHMEM_BEOS 0
+
+#define APR_USE_FLOCK_SERIALIZE 0
+#define APR_USE_SYSVSEM_SERIALIZE 1
+#define APR_USE_POSIXSEM_SERIALIZE 0
+#define APR_USE_FCNTL_SERIALIZE 0
+#define APR_USE_PROC_PTHREAD_SERIALIZE 0
+#define APR_USE_PTHREAD_SERIALIZE 1
+
+#define APR_HAS_FLOCK_SERIALIZE 1
+#define APR_HAS_SYSVSEM_SERIALIZE 1
+#define APR_HAS_POSIXSEM_SERIALIZE 1
+#define APR_HAS_FCNTL_SERIALIZE 1
+#define APR_HAS_PROC_PTHREAD_SERIALIZE 0
+
+#define APR_PROCESS_LOCK_IS_GLOBAL 0
+
+#define APR_HAVE_CORKABLE_TCP 1
+#define APR_HAVE_GETRLIMIT 1
+#define APR_HAVE_IN_ADDR 1
+#define APR_HAVE_INET_ADDR 1
+#define APR_HAVE_INET_NETWORK 1
+#define APR_HAVE_IPV6 1
+#define APR_HAVE_MEMMOVE 1
+#define APR_HAVE_SETRLIMIT 1
+#define APR_HAVE_SIGACTION 1
+#define APR_HAVE_SIGSUSPEND 1
+#define APR_HAVE_SIGWAIT 1
+#define APR_HAVE_SA_STORAGE 1
+#define APR_HAVE_STRCASECMP 1
+#define APR_HAVE_STRDUP 1
+#define APR_HAVE_STRICMP 0
+#define APR_HAVE_STRNCASECMP 1
+#define APR_HAVE_STRNICMP 0
+#define APR_HAVE_STRSTR 1
+#define APR_HAVE_MEMCHR 1
+#define APR_HAVE_STRUCT_RLIMIT 1
+#define APR_HAVE_UNION_SEMUN 1
+#define APR_HAVE_SCTP 0
+#define APR_HAVE_IOVEC 1
+
+/* APR Feature Macros */
+#define APR_HAS_SHARED_MEMORY 1
+#define APR_HAS_THREADS 1
+#define APR_HAS_SENDFILE 1
+#define APR_HAS_MMAP 1
+#define APR_HAS_FORK 1
+#define APR_HAS_RANDOM 1
+#define APR_HAS_OTHER_CHILD 1
+#define APR_HAS_DSO 1
+#define APR_HAS_SO_ACCEPTFILTER 0
+#define APR_HAS_UNICODE_FS 0
+#define APR_HAS_PROC_INVOKED 0
+#define APR_HAS_USER 1
+#define APR_HAS_LARGE_FILES 1
+#define APR_HAS_XTHREAD_FILES 0
+#define APR_HAS_OS_UUID 1
+
+#define APR_PROCATTR_USER_SET_REQUIRES_PASSWORD 0
+
+/* APR sets APR_FILES_AS_SOCKETS to 1 on systems where it is possible
+ * to poll on files/pipes.
+ */
+#define APR_FILES_AS_SOCKETS 1
+
+/* This macro indicates whether or not EBCDIC is the native character set.
+ */
+#define APR_CHARSET_EBCDIC 0
+
+/* If we have a TCP implementation that can be "corked", what flag
+ * do we use?
+ */
+#define APR_TCP_NOPUSH_FLAG TCP_NOPUSH
+
+/* Is the TCP_NODELAY socket option inherited from listening sockets?
+*/
+#define APR_TCP_NODELAY_INHERITED 1
+
+/* Is the O_NONBLOCK flag inherited from listening sockets?
+*/
+#define APR_O_NONBLOCK_INHERITED 1
+
+/* Typedefs that APR needs. */
+
+typedef unsigned char apr_byte_t;
+
+typedef short apr_int16_t;
+typedef unsigned short apr_uint16_t;
+
+typedef int apr_int32_t;
+typedef unsigned int apr_uint32_t;
+
+typedef long long apr_int64_t;
+typedef unsigned long long apr_uint64_t;
+
+typedef size_t apr_size_t;
+typedef ssize_t apr_ssize_t;
+typedef off_t apr_off_t;
+typedef socklen_t apr_socklen_t;
+typedef ino_t apr_ino_t;
+
+#define APR_SIZEOF_VOIDP 4
+
+#if APR_SIZEOF_VOIDP == 8
+typedef apr_uint64_t apr_uintptr_t;
+#else
+typedef apr_uint32_t apr_uintptr_t;
+#endif
+
+/* Are we big endian? */
+#define APR_IS_BIGENDIAN 0
+
+/* Mechanisms to properly type numeric literals */
+#define APR_INT64_C(val) INT64_C(val)
+#define APR_UINT64_C(val) UINT64_C(val)
+
+#ifdef INT16_MIN
+#define APR_INT16_MIN INT16_MIN
+#else
+#define APR_INT16_MIN (-0x7fff - 1)
+#endif
+
+#ifdef INT16_MAX
+#define APR_INT16_MAX INT16_MAX
+#else
+#define APR_INT16_MAX (0x7fff)
+#endif
+
+#ifdef UINT16_MAX
+#define APR_UINT16_MAX UINT16_MAX
+#else
+#define APR_UINT16_MAX (0xffff)
+#endif
+
+#ifdef INT32_MIN
+#define APR_INT32_MIN INT32_MIN
+#else
+#define APR_INT32_MIN (-0x7fffffff - 1)
+#endif
+
+#ifdef INT32_MAX
+#define APR_INT32_MAX INT32_MAX
+#else
+#define APR_INT32_MAX 0x7fffffff
+#endif
+
+#ifdef UINT32_MAX
+#define APR_UINT32_MAX UINT32_MAX
+#else
+#define APR_UINT32_MAX (0xffffffffU)
+#endif
+
+#ifdef INT64_MIN
+#define APR_INT64_MIN INT64_MIN
+#else
+#define APR_INT64_MIN (APR_INT64_C(-0x7fffffffffffffff) - 1)
+#endif
+
+#ifdef INT64_MAX
+#define APR_INT64_MAX INT64_MAX
+#else
+#define APR_INT64_MAX APR_INT64_C(0x7fffffffffffffff)
+#endif
+
+#ifdef UINT64_MAX
+#define APR_UINT64_MAX UINT64_MAX
+#else
+#define APR_UINT64_MAX APR_UINT64_C(0xffffffffffffffff)
+#endif
+
+#define APR_SIZE_MAX (~((apr_size_t)0))
+
+
+/* Definitions that APR programs need to work properly. */
+
+/**
+ * APR public API wrap for C++ compilers.
+ */
+#ifdef __cplusplus
+#define APR_BEGIN_DECLS extern "C" {
+#define APR_END_DECLS }
+#else
+#define APR_BEGIN_DECLS
+#define APR_END_DECLS
+#endif
+
+/**
+ * Thread callbacks from APR functions must be declared with APR_THREAD_FUNC,
+ * so that they follow the platform's calling convention.
+ * <PRE>
+ *
+ * void* APR_THREAD_FUNC my_thread_entry_fn(apr_thread_t *thd, void *data);
+ *
+ * </PRE>
+ */
+#define APR_THREAD_FUNC
+
+/**
+ * The public APR functions are declared with APR_DECLARE(), so they may
+ * use the most appropriate calling convention. Public APR functions with
+ * variable arguments must use APR_DECLARE_NONSTD().
+ *
+ * @remark Both the declaration and implementations must use the same macro.
+ *
+ * <PRE>
+ * APR_DECLARE(rettype) apr_func(args)
+ * </PRE>
+ * @see APR_DECLARE_NONSTD @see APR_DECLARE_DATA
+ * @remark Note that when APR compiles the library itself, it passes the
+ * symbol -DAPR_DECLARE_EXPORT to the compiler on some platforms (e.g. Win32)
+ * to export public symbols from the dynamic library build.\n
+ * The user must define the APR_DECLARE_STATIC when compiling to target
+ * the static APR library on some platforms (e.g. Win32.) The public symbols
+ * are neither exported nor imported when APR_DECLARE_STATIC is defined.\n
+ * By default, compiling an application and including the APR public
+ * headers, without defining APR_DECLARE_STATIC, will prepare the code to be
+ * linked to the dynamic library.
+ */
+#define APR_DECLARE(type) type
+
+/**
+ * The public APR functions using variable arguments are declared with
+ * APR_DECLARE_NONSTD(), as they must follow the C language calling convention.
+ * @see APR_DECLARE @see APR_DECLARE_DATA
+ * @remark Both the declaration and implementations must use the same macro.
+ * <PRE>
+ *
+ * APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ *
+ * </PRE>
+ */
+#define APR_DECLARE_NONSTD(type) type
+
+/**
+ * The public APR variables are declared with AP_MODULE_DECLARE_DATA.
+ * This assures the appropriate indirection is invoked at compile time.
+ * @see APR_DECLARE @see APR_DECLARE_NONSTD
+ * @remark Note that the declaration and implementations use different forms,
+ * but both must include the macro.
+ *
+ * <PRE>
+ *
+ * extern APR_DECLARE_DATA type apr_variable;\n
+ * APR_DECLARE_DATA type apr_variable = value;
+ *
+ * </PRE>
+ */
+#define APR_DECLARE_DATA
+
+/* Define APR_SSIZE_T_FMT.
+ * If ssize_t is an integer we define it to be "d",
+ * if ssize_t is a long int we define it to be "ld",
+ * if ssize_t is neither we declare an error here.
+ * I looked for a better way to define this here, but couldn't find one, so
+ * to find the logic for this definition search for "ssize_t_fmt" in
+ * configure.in.
+ */
+#define APR_SSIZE_T_FMT "ld"
+
+/* And APR_SIZE_T_FMT */
+#define APR_SIZE_T_FMT "lu"
+
+/* And APR_OFF_T_FMT */
+#define APR_OFF_T_FMT APR_INT64_T_FMT
+
+/* And APR_PID_T_FMT */
+#define APR_PID_T_FMT "d"
+
+/* And APR_INT64_T_FMT */
+#define APR_INT64_T_FMT "lld"
+
+/* And APR_UINT64_T_FMT */
+#define APR_UINT64_T_FMT "llu"
+
+/* And APR_UINT64_T_HEX_FMT */
+#define APR_UINT64_T_HEX_FMT "llx"
+
+/* Does the proc mutex lock threads too */
+#define APR_PROC_MUTEX_IS_GLOBAL 0
+
+/* Local machine definition for console and log output. */
+#define APR_EOL_STR "\n"
+
+#if APR_HAVE_SYS_WAIT_H
+#ifdef WEXITSTATUS
+#define apr_wait_t int
+#else
+#define apr_wait_t union wait
+#define WEXITSTATUS(status) (int)((status).w_retcode)
+#define WTERMSIG(status) (int)((status).w_termsig)
+#endif /* !WEXITSTATUS */
+#elif defined(__MINGW32__)
+typedef int apr_wait_t;
+#endif /* HAVE_SYS_WAIT_H */
+
+#if defined(PATH_MAX)
+#define APR_PATH_MAX PATH_MAX
+#elif defined(_POSIX_PATH_MAX)
+#define APR_PATH_MAX _POSIX_PATH_MAX
+#else
+#error no decision has been made on APR_PATH_MAX for your platform
+#endif
+
+#define APR_DSOPATH "DYLD_LIBRARY_PATH"
+
+/** @} */
+
+/* Definitions that only Win32 programs need to compile properly. */
+
+/* XXX These simply don't belong here, perhaps in apr_portable.h
+ * based on some APR_HAVE_PID/GID/UID?
+ */
+#ifdef __MINGW32__
+#ifndef __GNUC__
+typedef int pid_t;
+#endif
+typedef int uid_t;
+typedef int gid_t;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APR_H */
--- /dev/null
+/* include/arch/unix/apr_private.h. Generated from apr_private.h.in by configure. */
+/* include/arch/unix/apr_private.h.in. Generated from configure.in by autoheader. */
+
+
+#ifndef APR_PRIVATE_H
+#define APR_PRIVATE_H
+
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Define as function which can be used for conversion of strings to
+ apr_int64_t */
+#define APR_INT64_STRFN strtoll
+
+/* Define as function used for conversion of strings to apr_off_t */
+#define APR_OFF_T_STRFN strtoll
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to path of random device */
+#define DEV_RANDOM "/dev/urandom"
+
+/* Define if struct dirent has an inode member */
+#define DIRENT_INODE d_fileno
+
+/* Define if struct dirent has a d_type member */
+#define DIRENT_TYPE d_type
+
+/* Define if DSO support uses dlfcn.h */
+#define DSO_USE_DLFCN 1
+
+/* Define if DSO support uses dyld.h */
+/* #undef DSO_USE_DYLD */
+
+/* Define if DSO support uses shl_load */
+/* #undef DSO_USE_SHL */
+
+/* Define to list of paths to EGD sockets */
+/* #undef EGD_DEFAULT_SOCKET */
+
+/* Define if fcntl locks affect threads within the process */
+/* #undef FCNTL_IS_GLOBAL */
+
+/* Define if fcntl returns EACCES when F_SETLK is already held */
+/* #undef FCNTL_TRYACQUIRE_EACCES */
+
+/* Define if flock locks affect threads within the process */
+/* #undef FLOCK_IS_GLOBAL */
+
+/* Define if gethostbyaddr is thread safe */
+/* #undef GETHOSTBYADDR_IS_THREAD_SAFE */
+
+/* Define if gethostbyname is thread safe */
+/* #undef GETHOSTBYNAME_IS_THREAD_SAFE */
+
+/* Define if gethostbyname_r has the glibc style */
+/* #undef GETHOSTBYNAME_R_GLIBC2 */
+
+/* Define if gethostbyname_r has the hostent_data for the third argument */
+/* #undef GETHOSTBYNAME_R_HOSTENT_DATA */
+
+/* Define if getservbyname is thread safe */
+/* #undef GETSERVBYNAME_IS_THREAD_SAFE */
+
+/* Define if getservbyname_r has the glibc style */
+/* #undef GETSERVBYNAME_R_GLIBC2 */
+
+/* Define if getservbyname_r has the OSF/1 style */
+/* #undef GETSERVBYNAME_R_OSF1 */
+
+/* Define if getservbyname_r has the Solaris style */
+/* #undef GETSERVBYNAME_R_SOLARIS */
+
+/* Define if accept4 function is supported */
+/* #undef HAVE_ACCEPT4 */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#define HAVE_ARPA_INET_H 1
+
+/* Define if compiler provides atomic builtins */
+/* #undef HAVE_ATOMIC_BUILTINS */
+
+/* Define if BONE_VERSION is defined in sys/socket.h */
+/* #undef HAVE_BONE_VERSION */
+
+/* Define to 1 if you have the <ByteOrder.h> header file. */
+/* #undef HAVE_BYTEORDER_H */
+
+/* Define to 1 if you have the `calloc' function. */
+#define HAVE_CALLOC 1
+
+/* Define to 1 if you have the <conio.h> header file. */
+/* #undef HAVE_CONIO_H */
+
+/* Define to 1 if you have the `create_area' function. */
+/* #undef HAVE_CREATE_AREA */
+
+/* Define to 1 if you have the `create_sem' function. */
+/* #undef HAVE_CREATE_SEM */
+
+/* Define to 1 if you have the <crypt.h> header file. */
+/* #undef HAVE_CRYPT_H */
+
+/* Define to 1 if you have the <ctype.h> header file. */
+#define HAVE_CTYPE_H 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dir.h> header file. */
+/* #undef HAVE_DIR_H */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <dl.h> header file. */
+/* #undef HAVE_DL_H */
+
+/* Define if dup3 function is supported */
+/* #undef HAVE_DUP3 */
+
+/* Define if EGD is supported */
+/* #undef HAVE_EGD */
+
+/* Define if the epoll interface is supported */
+/* #undef HAVE_EPOLL */
+
+/* Define if epoll_create1 function is supported */
+/* #undef HAVE_EPOLL_CREATE1 */
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdatasync' function. */
+/* #undef HAVE_FDATASYNC */
+
+/* Define to 1 if you have the `flock' function. */
+#define HAVE_FLOCK 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define if F_SETLK is defined in fcntl.h */
+#define HAVE_F_SETLK 1
+
+/* Define if getaddrinfo accepts the AI_ADDRCONFIG flag */
+#define HAVE_GAI_ADDRCONFIG 1
+
+/* Define to 1 if you have the `gai_strerror' function. */
+#define HAVE_GAI_STRERROR 1
+
+/* Define if getaddrinfo exists and works well enough for APR */
+#define HAVE_GETADDRINFO 1
+
+/* Define to 1 if you have the `getenv' function. */
+#define HAVE_GETENV 1
+
+/* Define to 1 if you have the `getgrgid_r' function. */
+#define HAVE_GETGRGID_R 1
+
+/* Define to 1 if you have the `getgrnam_r' function. */
+#define HAVE_GETGRNAM_R 1
+
+/* Define to 1 if you have the `gethostbyaddr_r' function. */
+/* #undef HAVE_GETHOSTBYADDR_R */
+
+/* Define to 1 if you have the `gethostbyname_r' function. */
+/* #undef HAVE_GETHOSTBYNAME_R */
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define HAVE_GETIFADDRS 1
+
+/* Define if getnameinfo exists */
+#define HAVE_GETNAMEINFO 1
+
+/* Define to 1 if you have the `getpass' function. */
+#define HAVE_GETPASS 1
+
+/* Define to 1 if you have the `getpassphrase' function. */
+/* #undef HAVE_GETPASSPHRASE */
+
+/* Define to 1 if you have the `getpwnam_r' function. */
+#define HAVE_GETPWNAM_R 1
+
+/* Define to 1 if you have the `getpwuid_r' function. */
+#define HAVE_GETPWUID_R 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have the `getservbyname_r' function. */
+/* #undef HAVE_GETSERVBYNAME_R */
+
+/* Define to 1 if you have the `gmtime_r' function. */
+#define HAVE_GMTIME_R 1
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define if hstrerror is present */
+/* #undef HAVE_HSTRERROR */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <io.h> header file. */
+/* #undef HAVE_IO_H */
+
+/* Define to 1 if you have the `isinf' function. */
+#define HAVE_ISINF 1
+
+/* Define to 1 if you have the `isnan' function. */
+#define HAVE_ISNAN 1
+
+/* Define to 1 if you have the <kernel/OS.h> header file. */
+/* #undef HAVE_KERNEL_OS_H */
+
+/* Define to 1 if you have the `kqueue' function. */
+/* #undef HAVE_KQUEUE */
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBADVAPI32 */
+
+/* Define to 1 if you have the `bsd' library (-lbsd). */
+/* #undef HAVE_LIBBSD */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBKERNEL32 */
+
+/* Define to 1 if you have the `msvcrt' library (-lmsvcrt). */
+/* #undef HAVE_LIBMSVCRT */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBRPCRT4 */
+
+/* Define to 1 if you have the `sendfile' library (-lsendfile). */
+/* #undef HAVE_LIBSENDFILE */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBSHELL32 */
+
+/* Define to 1 if you have the `truerand' library (-ltruerand). */
+/* #undef HAVE_LIBTRUERAND */
+
+/* Enable if this library is available */
+/* #undef HAVE_LIBWS2_32 */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the `localtime_r' function. */
+#define HAVE_LOCALTIME_R 1
+
+/* Define if LOCK_EX is defined in sys/file.h */
+#define HAVE_LOCK_EX 1
+
+/* Define to 1 if you have the <mach-o/dyld.h> header file. */
+#define HAVE_MACH_O_DYLD_H 1
+
+/* Define to 1 if you have the <malloc.h> header file. */
+/* #undef HAVE_MALLOC_H */
+
+/* Define if MAP_ANON is defined in sys/mman.h */
+#define HAVE_MAP_ANON 1
+
+/* Define to 1 if you have the `memchr' function. */
+#define HAVE_MEMCHR 1
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mkstemp64' function. */
+/* #undef HAVE_MKSTEMP64 */
+
+/* Define to 1 if you have the `mmap' function. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the `mmap64' function. */
+/* #undef HAVE_MMAP64 */
+
+/* Define to 1 if you have the `munmap' function. */
+#define HAVE_MUNMAP 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <netinet/sctp.h> header file. */
+/* #undef HAVE_NETINET_SCTP_H */
+
+/* Define to 1 if you have the <netinet/sctp_uio.h> header file. */
+/* #undef HAVE_NETINET_SCTP_UIO_H */
+
+/* Defined if netinet/tcp.h is present */
+#define HAVE_NETINET_TCP_H 1
+
+/* Define to 1 if you have the <net/errno.h> header file. */
+/* #undef HAVE_NET_ERRNO_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#define HAVE_NL_LANGINFO 1
+
+/* Define to 1 if you have the <os2.h> header file. */
+/* #undef HAVE_OS2_H */
+
+/* Define to 1 if you have the <osreldate.h> header file. */
+/* #undef HAVE_OSRELDATE_H */
+
+/* Define to 1 if you have the <OS.h> header file. */
+/* #undef HAVE_OS_H */
+
+/* Define to 1 if you have the `poll' function. */
+/* #undef HAVE_POLL */
+
+/* Define if POLLIN is defined */
+#define HAVE_POLLIN 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#define HAVE_POLL_H 1
+
+/* Define to 1 if you have the `port_create' function. */
+/* #undef HAVE_PORT_CREATE */
+
+/* Define to 1 if you have the <process.h> header file. */
+/* #undef HAVE_PROCESS_H */
+
+/* Define to 1 if you have the `pthread_attr_setguardsize' function. */
+#define HAVE_PTHREAD_ATTR_SETGUARDSIZE 1
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* Define to 1 if you have the `pthread_key_delete' function. */
+#define HAVE_PTHREAD_KEY_DELETE 1
+
+/* Define to 1 if you have the `pthread_mutexattr_setpshared' function. */
+#define HAVE_PTHREAD_MUTEXATTR_SETPSHARED 1
+
+/* Define if recursive pthread mutexes are available */
+#define HAVE_PTHREAD_MUTEX_RECURSIVE 1
+
+/* Define if cross-process robust mutexes are available */
+/* #undef HAVE_PTHREAD_MUTEX_ROBUST */
+
+/* Define if PTHREAD_PROCESS_SHARED is defined in pthread.h */
+#define HAVE_PTHREAD_PROCESS_SHARED 1
+
+/* Define if pthread rwlocks are available */
+#define HAVE_PTHREAD_RWLOCKS 1
+
+/* Define to 1 if you have the `pthread_rwlock_init' function. */
+#define HAVE_PTHREAD_RWLOCK_INIT 1
+
+/* Define to 1 if you have the `pthread_yield' function. */
+/* #undef HAVE_PTHREAD_YIELD */
+
+/* Define to 1 if you have the `putenv' function. */
+#define HAVE_PUTENV 1
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `readdir64_r' function. */
+/* #undef HAVE_READDIR64_R */
+
+/* Define to 1 if you have the <sched.h> header file. */
+#define HAVE_SCHED_H 1
+
+/* Define to 1 if you have the `sched_yield' function. */
+#define HAVE_SCHED_YIELD 1
+
+/* Define to 1 if you have the <semaphore.h> header file. */
+#define HAVE_SEMAPHORE_H 1
+
+/* Define to 1 if you have the `semctl' function. */
+#define HAVE_SEMCTL 1
+
+/* Define to 1 if you have the `semget' function. */
+#define HAVE_SEMGET 1
+
+/* Define to 1 if you have the `sem_close' function. */
+#define HAVE_SEM_CLOSE 1
+
+/* Define to 1 if you have the `sem_post' function. */
+#define HAVE_SEM_POST 1
+
+/* Define if SEM_UNDO is defined in sys/sem.h */
+#define HAVE_SEM_UNDO 1
+
+/* Define to 1 if you have the `sem_unlink' function. */
+#define HAVE_SEM_UNLINK 1
+
+/* Define to 1 if you have the `sem_wait' function. */
+#define HAVE_SEM_WAIT 1
+
+/* Define to 1 if you have the `sendfile' function. */
+#define HAVE_SENDFILE 1
+
+/* Define to 1 if you have the `sendfile64' function. */
+/* #undef HAVE_SENDFILE64 */
+
+/* Define to 1 if you have the `sendfilev' function. */
+/* #undef HAVE_SENDFILEV */
+
+/* Define to 1 if you have the `sendfilev64' function. */
+/* #undef HAVE_SENDFILEV64 */
+
+/* Define to 1 if you have the `send_file' function. */
+/* #undef HAVE_SEND_FILE */
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define to 1 if you have the `set_h_errno' function. */
+/* #undef HAVE_SET_H_ERRNO */
+
+/* Define to 1 if you have the `shmat' function. */
+#define HAVE_SHMAT 1
+
+/* Define to 1 if you have the `shmctl' function. */
+#define HAVE_SHMCTL 1
+
+/* Define to 1 if you have the `shmdt' function. */
+#define HAVE_SHMDT 1
+
+/* Define to 1 if you have the `shmget' function. */
+#define HAVE_SHMGET 1
+
+/* Define to 1 if you have the `shm_open' function. */
+#define HAVE_SHM_OPEN 1
+
+/* Define to 1 if you have the `shm_unlink' function. */
+#define HAVE_SHM_UNLINK 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#define HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the `sigsuspend' function. */
+#define HAVE_SIGSUSPEND 1
+
+/* Define to 1 if you have the `sigwait' function. */
+#define HAVE_SIGWAIT 1
+
+/* Whether you have socklen_t */
+#define HAVE_SOCKLEN_T 1
+
+/* Define if the SOCK_CLOEXEC flag is supported */
+/* #undef HAVE_SOCK_CLOEXEC */
+
+/* Define if SO_ACCEPTFILTER is defined in sys/socket.h */
+/* #undef HAVE_SO_ACCEPTFILTER */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#define HAVE_STDDEF_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror_r' function. */
+#define HAVE_STRERROR_R 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strstr' function. */
+#define HAVE_STRSTR 1
+
+/* Define if struct impreq was found */
+#define HAVE_STRUCT_IPMREQ 1
+
+/* Define to 1 if `st_atimensec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIMENSEC */
+
+/* Define to 1 if `st_atime_n' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIME_N */
+
+/* Define to 1 if `st_atim.tv_nsec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC */
+
+/* Define to 1 if `st_blocks' is a member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_BLOCKS 1
+
+/* Define to 1 if `st_ctimensec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIMENSEC */
+
+/* Define to 1 if `st_ctime_n' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIME_N */
+
+/* Define to 1 if `st_ctim.tv_nsec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC */
+
+/* Define to 1 if `st_mtimensec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIMENSEC */
+
+/* Define to 1 if `st_mtime_n' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIME_N */
+
+/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC */
+
+/* Define to 1 if `tm_gmtoff' is a member of `struct tm'. */
+#define HAVE_STRUCT_TM_TM_GMTOFF 1
+
+/* Define to 1 if `__tm_gmtoff' is a member of `struct tm'. */
+/* #undef HAVE_STRUCT_TM___TM_GMTOFF */
+
+/* Define to 1 if you have the <sysapi.h> header file. */
+/* #undef HAVE_SYSAPI_H */
+
+/* Define to 1 if you have the <sysgtime.h> header file. */
+/* #undef HAVE_SYSGTIME_H */
+
+/* Define to 1 if you have the <sys/file.h> header file. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/mutex.h> header file. */
+/* #undef HAVE_SYS_MUTEX_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+#define HAVE_SYS_POLL_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/sem.h> header file. */
+#define HAVE_SYS_SEM_H 1
+
+/* Define to 1 if you have the <sys/sendfile.h> header file. */
+/* #undef HAVE_SYS_SENDFILE_H */
+
+/* Define to 1 if you have the <sys/shm.h> header file. */
+#define HAVE_SYS_SHM_H 1
+
+/* Define to 1 if you have the <sys/signal.h> header file. */
+#define HAVE_SYS_SIGNAL_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/sockio.h> header file. */
+#define HAVE_SYS_SOCKIO_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysctl.h> header file. */
+#define HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the <sys/syslimits.h> header file. */
+#define HAVE_SYS_SYSLIMITS_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+#define HAVE_SYS_UIO_H 1
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#define HAVE_SYS_UN_H 1
+
+/* Define to 1 if you have the <sys/uuid.h> header file. */
+/* #undef HAVE_SYS_UUID_H */
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define if TCP_CORK is defined in netinet/tcp.h */
+/* #undef HAVE_TCP_CORK */
+
+/* Define if TCP_NODELAY and TCP_CORK can be enabled at the same time */
+/* #undef HAVE_TCP_NODELAY_WITH_CORK */
+
+/* Define if TCP_NOPUSH is defined in netinet/tcp.h */
+#define HAVE_TCP_NOPUSH 1
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the <tpfeq.h> header file. */
+/* #undef HAVE_TPFEQ_H */
+
+/* Define to 1 if you have the <tpfio.h> header file. */
+/* #undef HAVE_TPFIO_H */
+
+/* Define if truerand is supported */
+/* #undef HAVE_TRUERAND */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <unix.h> header file. */
+/* #undef HAVE_UNIX_H */
+
+/* Define to 1 if you have the `unsetenv' function. */
+#define HAVE_UNSETENV 1
+
+/* Define to 1 if you have the `utime' function. */
+#define HAVE_UTIME 1
+
+/* Define to 1 if you have the `utimes' function. */
+#define HAVE_UTIMES 1
+
+/* Define to 1 if you have the `uuid_create' function. */
+/* #undef HAVE_UUID_CREATE */
+
+/* Define to 1 if you have the `uuid_generate' function. */
+#define HAVE_UUID_GENERATE 1
+
+/* Define to 1 if you have the <uuid.h> header file. */
+/* #undef HAVE_UUID_H */
+
+/* Define to 1 if you have the <uuid/uuid.h> header file. */
+#define HAVE_UUID_UUID_H 1
+
+/* Define if C compiler supports VLA */
+#define HAVE_VLA 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if you have the <windows.h> header file. */
+/* #undef HAVE_WINDOWS_H */
+
+/* Define to 1 if you have the <winsock2.h> header file. */
+/* #undef HAVE_WINSOCK2_H */
+
+/* Define to 1 if you have the `writev' function. */
+#define HAVE_WRITEV 1
+
+/* Define for z/OS pthread API nuances */
+/* #undef HAVE_ZOS_PTHREADS */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Define if EAI_ error codes from getaddrinfo are negative */
+/* #undef NEGATIVE_EAI */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define if POSIX semaphores affect threads within the process */
+#define POSIXSEM_IS_GLOBAL 1
+
+/* Define on PowerPC 405 where errata 77 applies */
+/* #undef PPC405_ERRATA */
+
+/* Define if pthread_attr_getdetachstate() has one arg */
+/* #undef PTHREAD_ATTR_GETDETACHSTATE_TAKES_ONE_ARG */
+
+/* Define if pthread_getspecific() has two args */
+/* #undef PTHREAD_GETSPECIFIC_TAKES_TWO_ARGS */
+
+/* Define if readdir is thread safe */
+/* #undef READDIR_IS_THREAD_SAFE */
+
+/* Define to 1 if the `setpgrp' function takes no argument. */
+#define SETPGRP_VOID 1
+
+/* */
+/* #undef SIGWAIT_TAKES_ONE_ARG */
+
+/* The size of `char', as computed by sizeof. */
+#define SIZEOF_CHAR 1
+
+/* The size of `int', as computed by sizeof. */
+#define SIZEOF_INT 4
+
+/* The size of `long', as computed by sizeof. */
+#define SIZEOF_LONG 4
+
+/* The size of `long long', as computed by sizeof. */
+#define SIZEOF_LONG_LONG 8
+
+/* The size of off_t */
+#define SIZEOF_OFF_T 8
+
+/* The size of pid_t */
+#define SIZEOF_PID_T 4
+
+/* The size of `short', as computed by sizeof. */
+#define SIZEOF_SHORT 2
+
+/* The size of size_t */
+#define SIZEOF_SIZE_T 4
+
+/* The size of ssize_t */
+#define SIZEOF_SSIZE_T 4
+
+/* The size of struct iovec */
+#define SIZEOF_STRUCT_IOVEC 8
+
+/* The size of `void*', as computed by sizeof. */
+#define SIZEOF_VOIDP 4
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if strerror returns int */
+#define STRERROR_R_RC_INT 1
+
+/* Define if SysV semaphores affect threads within the process */
+/* #undef SYSVSEM_IS_GLOBAL */
+
+/* Define if use of generic atomics is requested */
+/* #undef USE_ATOMICS_GENERIC */
+
+/* Define if BeOS Semaphores will be used */
+/* #undef USE_BEOSSEM */
+
+/* Define if SVR4-style fcntl() will be used */
+/* #undef USE_FCNTL_SERIALIZE */
+
+/* Define if 4.2BSD-style flock() will be used */
+/* #undef USE_FLOCK_SERIALIZE */
+
+/* Define if BeOS areas will be used */
+/* #undef USE_SHMEM_BEOS */
+
+/* Define if BeOS areas will be used */
+/* #undef USE_SHMEM_BEOS_ANON */
+
+/* Define if 4.4BSD-style mmap() via MAP_ANON will be used */
+#define USE_SHMEM_MMAP_ANON 1
+
+/* Define if mmap() via POSIX.1 shm_open() on temporary file will be used */
+/* #undef USE_SHMEM_MMAP_SHM */
+
+/* Define if Classical mmap() on temporary file will be used */
+/* #undef USE_SHMEM_MMAP_TMP */
+
+/* Define if SVR4-style mmap() on /dev/zero will be used */
+/* #undef USE_SHMEM_MMAP_ZERO */
+
+/* Define if OS/2 DosAllocSharedMem() will be used */
+/* #undef USE_SHMEM_OS2 */
+
+/* Define if OS/2 DosAllocSharedMem() will be used */
+/* #undef USE_SHMEM_OS2_ANON */
+
+/* Define if SysV IPC shmget() will be used */
+#define USE_SHMEM_SHMGET 1
+
+/* Define if SysV IPC shmget() will be used */
+/* #undef USE_SHMEM_SHMGET_ANON */
+
+/* Define if Windows shared memory will be used */
+/* #undef USE_SHMEM_WIN32 */
+
+/* Define if Windows CreateFileMapping() will be used */
+/* #undef USE_SHMEM_WIN32_ANON */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Define if SysV IPC semget() will be used */
+#define USE_SYSVSEM_SERIALIZE 1
+
+/* Define if apr_wait_for_io_or_timeout() uses poll(2) */
+/* #undef WAITIO_USES_POLL */
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+# define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+/* # undef WORDS_BIGENDIAN */
+# endif
+#endif
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef ssize_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+
+/* switch this on if we have a BeOS version below BONE */
+#if BEOS && !HAVE_BONE_VERSION
+#define BEOS_R5 1
+#else
+#define BEOS_BONE 1
+#endif
+
+/*
+ * Include common private declarations.
+ */
+#include "../apr_private_common.h"
+#endif /* APR_PRIVATE_H */
+
--- /dev/null
+# Copyright 2010 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.
+
+#
+# Notice: We do not include the dbd file in the source list.
+#
+
+{
+ 'variables': {
+ 'aprutil_root': '<(DEPTH)/third_party/apache/aprutil',
+ 'aprutil_src_root': '<(aprutil_root)/src',
+ 'aprutil_gen_os_root': '<(aprutil_root)/gen/arch/<(OS)',
+ 'aprutil_gen_arch_root': '<(aprutil_gen_os_root)/<(target_arch)',
+ 'system_include_path_aprutil%': '/usr/include/apr-1.0',
+ },
+ 'conditions': [
+ ['use_system_apache_dev==0', {
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(aprutil_src_root)/include',
+ '<(aprutil_gen_arch_root)/include',
+ ],
+ 'conditions': [
+ ['OS=="mac"', {
+ 'defines': [
+ 'HAVE_CONFIG_H',
+ 'DARWIN',
+ 'SIGPROCMASK_SETS_THREAD_MASK',
+ ]}],
+ ['OS=="linux"', {
+ 'defines': [
+ # We need to define _LARGEFILE64_SOURCE so <sys/types.h>
+ # provides off64_t.
+ '_LARGEFILE64_SOURCE',
+ 'HAVE_CONFIG_H',
+ 'LINUX=2',
+ '_REENTRANT',
+ '_GNU_SOURCE',
+ ],
+ }],
+ ],
+ },
+ },
+ {
+ 'target_name': 'aprutil',
+ 'type': '<(library)',
+ 'dependencies': [
+ 'include',
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:apr',
+ ],
+ 'export_dependent_settings': [
+ 'include',
+ ],
+ 'include_dirs': [
+ '<(aprutil_src_root)/include/private',
+ '<(aprutil_gen_arch_root)/include/private',
+ ],
+ 'sources': [
+ 'src/buckets/apr_brigade.c',
+ 'src/buckets/apr_buckets.c',
+ 'src/buckets/apr_buckets_alloc.c',
+ 'src/buckets/apr_buckets_eos.c',
+ 'src/buckets/apr_buckets_file.c',
+ 'src/buckets/apr_buckets_flush.c',
+ 'src/buckets/apr_buckets_heap.c',
+ 'src/buckets/apr_buckets_mmap.c',
+ 'src/buckets/apr_buckets_pipe.c',
+ 'src/buckets/apr_buckets_pool.c',
+ 'src/buckets/apr_buckets_refcount.c',
+ 'src/buckets/apr_buckets_simple.c',
+ 'src/buckets/apr_buckets_socket.c',
+ 'src/dbm/apr_dbm.c',
+ 'src/dbm/apr_dbm_sdbm.c',
+ 'src/dbm/sdbm/sdbm.c',
+ 'src/dbm/sdbm/sdbm_hash.c',
+ 'src/dbm/sdbm/sdbm_lock.c',
+ 'src/dbm/sdbm/sdbm_pair.c',
+ 'src/encoding/apr_base64.c',
+ 'src/hooks/apr_hooks.c',
+ 'src/ldap/apr_ldap_stub.c',
+ 'src/ldap/apr_ldap_url.c',
+ 'src/memcache/apr_memcache.c',
+ 'src/misc/apr_date.c',
+ 'src/misc/apr_queue.c',
+ 'src/misc/apr_reslist.c',
+ 'src/misc/apr_rmm.c',
+ 'src/misc/apr_thread_pool.c',
+ 'src/misc/apu_dso.c',
+ 'src/misc/apu_version.c',
+ 'src/strmatch/apr_strmatch.c',
+ 'src/uri/apr_uri.c',
+ 'src/xlate/xlate.c',
+ ],
+ 'conditions': [
+ ['OS!="win"', {
+ 'conditions': [
+ ['OS=="linux"', {
+ 'cflags': [
+ '-pthread',
+ '-Wall',
+ ],
+ }],
+ ],
+ }],
+ ],
+ }
+ ],
+ }, { # use_system_apache_dev
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(system_include_path_aprutil)',
+ ],
+ 'defines': [
+ # We need to define _LARGEFILE64_SOURCE so <sys/types.h>
+ # provides off64_t.
+ '_LARGEFILE64_SOURCE',
+ 'HAVE_CONFIG_H',
+ 'LINUX=2',
+ '_REENTRANT',
+ '_GNU_SOURCE',
+ ],
+ },
+ },
+ {
+ 'target_name': 'aprutil',
+ 'type': 'settings',
+ 'dependencies': [
+ 'include',
+ ],
+ 'export_dependent_settings': [
+ 'include',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-laprutil-1',
+ ],
+ },
+ },
+ ],
+ }],
+ ],
+}
+
--- /dev/null
+/* 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.
+ */
+
+/*
+ * apr_ldap.h is generated from apr_ldap.h.in by configure -- do not edit apr_ldap.h
+ */
+/**
+ * @file apr_ldap.h
+ * @brief APR-UTIL LDAP
+ */
+#ifndef APU_LDAP_H
+#define APU_LDAP_H
+
+/**
+ * @defgroup APR_Util_LDAP LDAP
+ * @ingroup APR_Util
+ * @{
+ */
+
+/* this will be defined if LDAP support was compiled into apr-util */
+#define APR_HAS_LDAP 0
+
+/* identify the LDAP toolkit used */
+#define APR_HAS_NETSCAPE_LDAPSDK 0
+#define APR_HAS_SOLARIS_LDAPSDK 0
+#define APR_HAS_NOVELL_LDAPSDK 0
+#define APR_HAS_MOZILLA_LDAPSDK 0
+#define APR_HAS_OPENLDAP_LDAPSDK 0
+#define APR_HAS_MICROSOFT_LDAPSDK 0
+#define APR_HAS_TIVOLI_LDAPSDK 0
+#define APR_HAS_ZOS_LDAPSDK 0
+#define APR_HAS_OTHER_LDAPSDK 0
+
+
+/*
+ * Handle the case when LDAP is enabled
+ */
+#if APR_HAS_LDAP
+
+/*
+ * The following #defines are DEPRECATED and should not be used for
+ * anything. They remain to maintain binary compatibility.
+ * The original code defined the OPENLDAP SDK as present regardless
+ * of what really was there, which was way bogus. In addition, the
+ * apr_ldap_url_parse*() functions have been rewritten specifically for
+ * APR, so the APR_HAS_LDAP_URL_PARSE macro is forced to zero.
+ */
+#if APR_HAS_TIVOLI_LDAPSDK
+#define APR_HAS_LDAP_SSL 0
+#else
+#define APR_HAS_LDAP_SSL 1
+#endif
+#define APR_HAS_LDAP_URL_PARSE 0
+
+#if APR_HAS_OPENLDAP_LDAPSDK && !defined(LDAP_DEPRECATED)
+/* Ensure that the "deprecated" interfaces are still exposed
+ * with OpenLDAP >= 2.3; these were exposed by default in earlier
+ * releases. */
+#define LDAP_DEPRECATED 1
+#endif
+
+/*
+ * Include the standard LDAP header files.
+ */
+
+
+
+
+
+
+/*
+ * Detected standard functions
+ */
+#define APR_HAS_LDAPSSL_CLIENT_INIT 0
+#define APR_HAS_LDAPSSL_CLIENT_DEINIT 0
+#define APR_HAS_LDAPSSL_ADD_TRUSTED_CERT 0
+#define APR_HAS_LDAP_START_TLS_S 0
+#define APR_HAS_LDAP_SSLINIT 0
+#define APR_HAS_LDAPSSL_INIT 0
+#define APR_HAS_LDAPSSL_INSTALL_ROUTINES 0
+
+/*
+ * Make sure the secure LDAP port is defined
+ */
+#ifndef LDAPS_PORT
+#define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
+#endif
+
+/*
+ * For ldap function calls that input a size limit on the number of returned elements
+ * Some SDKs do not have the define for LDAP_DEFAULT_LIMIT (-1) or LDAP_NO_LIMIT (0)
+ * LDAP_DEFAULT_LIMIT is preferred as it allows inheritance from whatever the SDK
+ * or process is configured for.
+ */
+#ifdef LDAP_DEFAULT_LIMIT
+#define APR_LDAP_SIZELIMIT LDAP_DEFAULT_LIMIT
+#else
+#ifdef LDAP_NO_LIMIT
+#define APR_LDAP_SIZELIMIT LDAP_NO_LIMIT
+#endif
+#endif
+
+#ifndef APR_LDAP_SIZELIMIT
+#define APR_LDAP_SIZELIMIT 0 /* equivalent to LDAP_NO_LIMIT, and what goes on the wire */
+#endif
+
+/*
+ * z/OS is missing some defines
+ */
+#ifndef LDAP_VERSION_MAX
+#define LDAP_VERSION_MAX LDAP_VERSION
+#endif
+#if APR_HAS_ZOS_LDAPSDK
+#define LDAP_VENDOR_NAME "IBM z/OS"
+#endif
+
+/* Note: Macros defining const casting has been removed in APR v1.0,
+ * pending real support for LDAP v2.0 toolkits.
+ *
+ * In the mean time, please use an LDAP v3.0 toolkit.
+ */
+#if LDAP_VERSION_MAX <= 2
+#error Support for LDAP v2.0 toolkits has been removed from apr-util. Please use an LDAP v3.0 toolkit.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * This structure allows the C LDAP API error codes to be returned
+ * along with plain text error messages that explain to us mere mortals
+ * what really happened.
+ */
+typedef struct apr_ldap_err_t {
+ const char *reason;
+ const char *msg;
+ int rc;
+} apr_ldap_err_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* The MS SDK returns LDAP_UNAVAILABLE when the backend has closed the connection
+ * between LDAP calls. Protect with APR_HAS_MICROSOFT_LDAPSDK in case someone
+ * manually chooses another SDK on Windows
+ */
+#if APR_HAS_MICROSOFT_LDAPSDK
+#define APR_LDAP_IS_SERVER_DOWN(s) ((s) == LDAP_SERVER_DOWN \
+ || (s) == LDAP_UNAVAILABLE)
+#else
+#define APR_LDAP_IS_SERVER_DOWN(s) ((s) == LDAP_SERVER_DOWN)
+#endif
+
+/* These symbols are not actually exported in a DSO build, but mapped into
+ * a private exported function array for apr_ldap_stub to bind dynamically.
+ * Rename them appropriately to protect the global namespace.
+ */
+#ifdef APU_DSO_LDAP_BUILD
+
+#define apr_ldap_info apr__ldap_info
+#define apr_ldap_init apr__ldap_init
+#define apr_ldap_ssl_init apr__ldap_ssl_init
+#define apr_ldap_ssl_deinit apr__ldap_ssl_deinit
+#define apr_ldap_get_option apr__ldap_get_option
+#define apr_ldap_set_option apr__ldap_set_option
+#define apr_ldap_rebind_init apr__ldap_rebind_init
+#define apr_ldap_rebind_add apr__ldap_rebind_add
+#define apr_ldap_rebind_remove apr__ldap_rebind_remove
+
+#define APU_DECLARE_LDAP(type) type
+#else
+#define APU_DECLARE_LDAP(type) APU_DECLARE(type)
+#endif
+
+#include "apr_ldap_url.h"
+#include "apr_ldap_init.h"
+#include "apr_ldap_option.h"
+#include "apr_ldap_rebind.h"
+
+/** @} */
+#endif /* APR_HAS_LDAP */
+#endif /* APU_LDAP_H */
--- /dev/null
+/* 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.
+ */
+
+/*
+ * apu.h is generated from apu.h.in by configure -- do not edit apu.h
+ */
+/* @file apu.h
+ * @brief APR-Utility main file
+ */
+/**
+ * @defgroup APR_Util APR Utility Functions
+ * @{
+ */
+
+
+#ifndef APU_H
+#define APU_H
+
+/**
+ * APU_DECLARE_EXPORT is defined when building the APR-UTIL dynamic library,
+ * so that all public symbols are exported.
+ *
+ * APU_DECLARE_STATIC is defined when including the APR-UTIL public headers,
+ * to provide static linkage when the dynamic library may be unavailable.
+ *
+ * APU_DECLARE_STATIC and APU_DECLARE_EXPORT are left undefined when
+ * including the APR-UTIL public headers, to import and link the symbols from
+ * the dynamic APR-UTIL library and assure appropriate indirection and calling
+ * conventions at compile time.
+ */
+
+/**
+ * The public APR-UTIL functions are declared with APU_DECLARE(), so they may
+ * use the most appropriate calling convention. Public APR functions with
+ * variable arguments must use APU_DECLARE_NONSTD().
+ *
+ * @fn APU_DECLARE(rettype) apr_func(args);
+ */
+#define APU_DECLARE(type) type
+/**
+ * The public APR-UTIL functions using variable arguments are declared with
+ * APU_DECLARE_NONSTD(), as they must use the C language calling convention.
+ *
+ * @fn APU_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ */
+#define APU_DECLARE_NONSTD(type) type
+/**
+ * The public APR-UTIL variables are declared with APU_DECLARE_DATA.
+ * This assures the appropriate indirection is invoked at compile time.
+ *
+ * @fn APU_DECLARE_DATA type apr_variable;
+ * @note APU_DECLARE_DATA extern type apr_variable; syntax is required for
+ * declarations within headers to properly import the variable.
+ */
+#define APU_DECLARE_DATA
+
+#if !defined(WIN32) || defined(APU_MODULE_DECLARE_STATIC)
+/**
+ * Declare a dso module's exported module structure as APU_MODULE_DECLARE_DATA.
+ *
+ * Unless APU_MODULE_DECLARE_STATIC is defined at compile time, symbols
+ * declared with APU_MODULE_DECLARE_DATA are always exported.
+ * @code
+ * module APU_MODULE_DECLARE_DATA mod_tag
+ * @endcode
+ */
+#define APU_MODULE_DECLARE_DATA
+#else
+#define APU_MODULE_DECLARE_DATA __declspec(dllexport)
+#endif
+
+/*
+ * we always have SDBM (it's in our codebase)
+ */
+#define APU_HAVE_SDBM 1
+#define APU_HAVE_GDBM 0
+#define APU_HAVE_NDBM 0
+#define APU_HAVE_DB 0
+
+#if APU_HAVE_DB
+#define APU_HAVE_DB_VERSION 0
+#endif
+
+#define APU_HAVE_PGSQL 1
+#define APU_HAVE_MYSQL 0
+#define APU_HAVE_SQLITE3 1
+#define APU_HAVE_SQLITE2 0
+#define APU_HAVE_ORACLE 0
+#define APU_HAVE_FREETDS 0
+#define APU_HAVE_ODBC 0
+
+#define APU_HAVE_APR_ICONV 0
+#define APU_HAVE_ICONV 1
+#define APR_HAS_XLATE (APU_HAVE_APR_ICONV || APU_HAVE_ICONV)
+
+#endif /* APU_H */
+/** @} */
--- /dev/null
+/* 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 "apu.h" /* configuration data */
+
+/**
+ * @file apu_want.h
+ * @brief APR Standard Headers Support
+ *
+ * <PRE>
+ * Features:
+ *
+ * APU_WANT_DB: <db.h>
+ *
+ * Typical usage:
+ *
+ * #define APU_WANT_DB
+ * #include "apu_want.h"
+ *
+ * The appropriate headers will be included.
+ *
+ * Note: it is safe to use this in a header (it won't interfere with other
+ * headers' or source files' use of apu_want.h)
+ * </PRE>
+ */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef APU_WANT_DB
+
+#if APU_HAVE_DB
+#include <db.h>
+#endif
+
+#undef APU_WANT_DB
+#endif
+
+/* --------------------------------------------------------------------- */
--- /dev/null
+/* include/private/apu_config.h. Generated from apu_config.h.in by configure. */
+/* include/private/apu_config.h.in. Generated from configure.in by autoheader. */
+
+/* Define if the system crypt() function is threadsafe */
+/* #undef APU_CRYPT_THREADSAFE */
+
+/* Define to 1 if modular components are built as DSOs */
+#define APU_DSO_BUILD 1
+
+/* Define to be absolute path to DSO directory */
+#define APU_DSO_LIBDIR "/usr/local/apr/lib/apr-util-1"
+
+/* Define if the inbuf parm to iconv() is const char ** */
+/* #undef APU_ICONV_INBUF_CONST */
+
+/* Define if crypt_r has uses CRYPTD */
+/* #undef CRYPT_R_CRYPTD */
+
+/* Define if crypt_r uses struct crypt_data */
+#define CRYPT_R_STRUCT_CRYPT_DATA 1
+
+/* Define if CODESET is defined in langinfo.h */
+#define HAVE_CODESET 1
+
+/* Define to 1 if you have the `crypt_r' function. */
+#define HAVE_CRYPT_R 1
+
+/* Define if expat.h is available */
+#define HAVE_EXPAT_H 1
+
+/* Define to 1 if you have the <freetds/sybdb.h> header file. */
+/* #undef HAVE_FREETDS_SYBDB_H */
+
+/* Define to 1 if you have the <iconv.h> header file. */
+#define HAVE_ICONV_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Define to 1 if you have the <lber.h> header file. */
+/* #undef HAVE_LBER_H */
+
+/* Defined if ldap.h is present */
+/* #undef HAVE_LDAP_H */
+
+/* Define to 1 if you have the <ldap_ssl.h> header file. */
+/* #undef HAVE_LDAP_SSL_H */
+
+/* Define to 1 if you have the <libpq-fe.h> header file. */
+#define HAVE_LIBPQ_FE_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <mysql.h> header file. */
+/* #undef HAVE_MYSQL_H */
+
+/* Define to 1 if you have the <mysql/mysql.h> header file. */
+/* #undef HAVE_MYSQL_MYSQL_H */
+
+/* Define to 1 if you have the <mysql/my_global.h> header file. */
+/* #undef HAVE_MYSQL_MY_GLOBAL_H */
+
+/* Define to 1 if you have the <mysql/my_sys.h> header file. */
+/* #undef HAVE_MYSQL_MY_SYS_H */
+
+/* Define to 1 if you have the <my_global.h> header file. */
+/* #undef HAVE_MY_GLOBAL_H */
+
+/* Define to 1 if you have the <my_sys.h> header file. */
+/* #undef HAVE_MY_SYS_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#define HAVE_NL_LANGINFO 1
+
+/* Define to 1 if you have the <oci.h> header file. */
+/* #undef HAVE_OCI_H */
+
+/* Define to 1 if you have the <odbc/sql.h> header file. */
+/* #undef HAVE_ODBC_SQL_H */
+
+/* Define to 1 if you have the <postgresql/libpq-fe.h> header file. */
+/* #undef HAVE_POSTGRESQL_LIBPQ_FE_H */
+
+/* Define to 1 if you have the <sqlite3.h> header file. */
+#define HAVE_SQLITE3_H 1
+
+/* Define to 1 if you have the <sqlite.h> header file. */
+/* #undef HAVE_SQLITE_H */
+
+/* Define to 1 if you have the <sql.h> header file. */
+/* #undef HAVE_SQL_H */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sybdb.h> header file. */
+/* #undef HAVE_SYBDB_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define if xmlparse/xmlparse.h is available */
+/* #undef HAVE_XMLPARSE_XMLPARSE_H */
+
+/* Define if xmltok/xmlparse.h is available */
+/* #undef HAVE_XMLTOK_XMLPARSE_H */
+
+/* Define if xml/xmlparse.h is available */
+/* #undef HAVE_XML_XMLPARSE_H */
+
+/* Define if ldap_set_rebind_proc takes three arguments */
+/* #undef LDAP_SET_REBIND_PROC_THREE */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
--- /dev/null
+/* 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.
+ */
+
+#ifndef APU_SELECT_DBM_H
+#define APU_SELECT_DBM_H
+
+/*
+** The following macros control what features APRUTIL will use
+*/
+#define APU_USE_SDBM 1
+#define APU_USE_NDBM 0
+#define APU_USE_GDBM 0
+#define APU_USE_DB 0
+
+#endif /* !APU_SELECT_DBM_H */
--- /dev/null
+/* 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.
+ */
+
+/*
+ * apr_ldap.h is generated from apr_ldap.h.in by configure -- do not edit apr_ldap.h
+ */
+/**
+ * @file apr_ldap.h
+ * @brief APR-UTIL LDAP
+ */
+#ifndef APU_LDAP_H
+#define APU_LDAP_H
+
+/**
+ * @defgroup APR_Util_LDAP LDAP
+ * @ingroup APR_Util
+ * @{
+ */
+
+/* this will be defined if LDAP support was compiled into apr-util */
+#define APR_HAS_LDAP 0
+
+/* identify the LDAP toolkit used */
+#define APR_HAS_NETSCAPE_LDAPSDK 0
+#define APR_HAS_SOLARIS_LDAPSDK 0
+#define APR_HAS_NOVELL_LDAPSDK 0
+#define APR_HAS_MOZILLA_LDAPSDK 0
+#define APR_HAS_OPENLDAP_LDAPSDK 0
+#define APR_HAS_MICROSOFT_LDAPSDK 0
+#define APR_HAS_TIVOLI_LDAPSDK 0
+#define APR_HAS_ZOS_LDAPSDK 0
+#define APR_HAS_OTHER_LDAPSDK 0
+
+
+/*
+ * Handle the case when LDAP is enabled
+ */
+#if APR_HAS_LDAP
+
+/*
+ * The following #defines are DEPRECATED and should not be used for
+ * anything. They remain to maintain binary compatibility.
+ * The original code defined the OPENLDAP SDK as present regardless
+ * of what really was there, which was way bogus. In addition, the
+ * apr_ldap_url_parse*() functions have been rewritten specifically for
+ * APR, so the APR_HAS_LDAP_URL_PARSE macro is forced to zero.
+ */
+#if APR_HAS_TIVOLI_LDAPSDK
+#define APR_HAS_LDAP_SSL 0
+#else
+#define APR_HAS_LDAP_SSL 1
+#endif
+#define APR_HAS_LDAP_URL_PARSE 0
+
+#if APR_HAS_OPENLDAP_LDAPSDK && !defined(LDAP_DEPRECATED)
+/* Ensure that the "deprecated" interfaces are still exposed
+ * with OpenLDAP >= 2.3; these were exposed by default in earlier
+ * releases. */
+#define LDAP_DEPRECATED 1
+#endif
+
+/*
+ * Include the standard LDAP header files.
+ */
+
+
+
+
+
+
+/*
+ * Detected standard functions
+ */
+#define APR_HAS_LDAPSSL_CLIENT_INIT 0
+#define APR_HAS_LDAPSSL_CLIENT_DEINIT 0
+#define APR_HAS_LDAPSSL_ADD_TRUSTED_CERT 0
+#define APR_HAS_LDAP_START_TLS_S 0
+#define APR_HAS_LDAP_SSLINIT 0
+#define APR_HAS_LDAPSSL_INIT 0
+#define APR_HAS_LDAPSSL_INSTALL_ROUTINES 0
+
+/*
+ * Make sure the secure LDAP port is defined
+ */
+#ifndef LDAPS_PORT
+#define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
+#endif
+
+/*
+ * For ldap function calls that input a size limit on the number of returned elements
+ * Some SDKs do not have the define for LDAP_DEFAULT_LIMIT (-1) or LDAP_NO_LIMIT (0)
+ * LDAP_DEFAULT_LIMIT is preferred as it allows inheritance from whatever the SDK
+ * or process is configured for.
+ */
+#ifdef LDAP_DEFAULT_LIMIT
+#define APR_LDAP_SIZELIMIT LDAP_DEFAULT_LIMIT
+#else
+#ifdef LDAP_NO_LIMIT
+#define APR_LDAP_SIZELIMIT LDAP_NO_LIMIT
+#endif
+#endif
+
+#ifndef APR_LDAP_SIZELIMIT
+#define APR_LDAP_SIZELIMIT 0 /* equivalent to LDAP_NO_LIMIT, and what goes on the wire */
+#endif
+
+/*
+ * z/OS is missing some defines
+ */
+#ifndef LDAP_VERSION_MAX
+#define LDAP_VERSION_MAX LDAP_VERSION
+#endif
+#if APR_HAS_ZOS_LDAPSDK
+#define LDAP_VENDOR_NAME "IBM z/OS"
+#endif
+
+/* Note: Macros defining const casting has been removed in APR v1.0,
+ * pending real support for LDAP v2.0 toolkits.
+ *
+ * In the mean time, please use an LDAP v3.0 toolkit.
+ */
+#if LDAP_VERSION_MAX <= 2
+#error Support for LDAP v2.0 toolkits has been removed from apr-util. Please use an LDAP v3.0 toolkit.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * This structure allows the C LDAP API error codes to be returned
+ * along with plain text error messages that explain to us mere mortals
+ * what really happened.
+ */
+typedef struct apr_ldap_err_t {
+ const char *reason;
+ const char *msg;
+ int rc;
+} apr_ldap_err_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* The MS SDK returns LDAP_UNAVAILABLE when the backend has closed the connection
+ * between LDAP calls. Protect with APR_HAS_MICROSOFT_LDAPSDK in case someone
+ * manually chooses another SDK on Windows
+ */
+#if APR_HAS_MICROSOFT_LDAPSDK
+#define APR_LDAP_IS_SERVER_DOWN(s) ((s) == LDAP_SERVER_DOWN \
+ || (s) == LDAP_UNAVAILABLE)
+#else
+#define APR_LDAP_IS_SERVER_DOWN(s) ((s) == LDAP_SERVER_DOWN)
+#endif
+
+/* These symbols are not actually exported in a DSO build, but mapped into
+ * a private exported function array for apr_ldap_stub to bind dynamically.
+ * Rename them appropriately to protect the global namespace.
+ */
+#ifdef APU_DSO_LDAP_BUILD
+
+#define apr_ldap_info apr__ldap_info
+#define apr_ldap_init apr__ldap_init
+#define apr_ldap_ssl_init apr__ldap_ssl_init
+#define apr_ldap_ssl_deinit apr__ldap_ssl_deinit
+#define apr_ldap_get_option apr__ldap_get_option
+#define apr_ldap_set_option apr__ldap_set_option
+#define apr_ldap_rebind_init apr__ldap_rebind_init
+#define apr_ldap_rebind_add apr__ldap_rebind_add
+#define apr_ldap_rebind_remove apr__ldap_rebind_remove
+
+#define APU_DECLARE_LDAP(type) type
+#else
+#define APU_DECLARE_LDAP(type) APU_DECLARE(type)
+#endif
+
+#include "apr_ldap_url.h"
+#include "apr_ldap_init.h"
+#include "apr_ldap_option.h"
+#include "apr_ldap_rebind.h"
+
+/** @} */
+#endif /* APR_HAS_LDAP */
+#endif /* APU_LDAP_H */
--- /dev/null
+/* 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.
+ */
+
+/*
+ * apu.h is generated from apu.h.in by configure -- do not edit apu.h
+ */
+/* @file apu.h
+ * @brief APR-Utility main file
+ */
+/**
+ * @defgroup APR_Util APR Utility Functions
+ * @{
+ */
+
+
+#ifndef APU_H
+#define APU_H
+
+/**
+ * APU_DECLARE_EXPORT is defined when building the APR-UTIL dynamic library,
+ * so that all public symbols are exported.
+ *
+ * APU_DECLARE_STATIC is defined when including the APR-UTIL public headers,
+ * to provide static linkage when the dynamic library may be unavailable.
+ *
+ * APU_DECLARE_STATIC and APU_DECLARE_EXPORT are left undefined when
+ * including the APR-UTIL public headers, to import and link the symbols from
+ * the dynamic APR-UTIL library and assure appropriate indirection and calling
+ * conventions at compile time.
+ */
+
+/**
+ * The public APR-UTIL functions are declared with APU_DECLARE(), so they may
+ * use the most appropriate calling convention. Public APR functions with
+ * variable arguments must use APU_DECLARE_NONSTD().
+ *
+ * @fn APU_DECLARE(rettype) apr_func(args);
+ */
+#define APU_DECLARE(type) type
+/**
+ * The public APR-UTIL functions using variable arguments are declared with
+ * APU_DECLARE_NONSTD(), as they must use the C language calling convention.
+ *
+ * @fn APU_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ */
+#define APU_DECLARE_NONSTD(type) type
+/**
+ * The public APR-UTIL variables are declared with APU_DECLARE_DATA.
+ * This assures the appropriate indirection is invoked at compile time.
+ *
+ * @fn APU_DECLARE_DATA type apr_variable;
+ * @note APU_DECLARE_DATA extern type apr_variable; syntax is required for
+ * declarations within headers to properly import the variable.
+ */
+#define APU_DECLARE_DATA
+
+#if !defined(WIN32) || defined(APU_MODULE_DECLARE_STATIC)
+/**
+ * Declare a dso module's exported module structure as APU_MODULE_DECLARE_DATA.
+ *
+ * Unless APU_MODULE_DECLARE_STATIC is defined at compile time, symbols
+ * declared with APU_MODULE_DECLARE_DATA are always exported.
+ * @code
+ * module APU_MODULE_DECLARE_DATA mod_tag
+ * @endcode
+ */
+#define APU_MODULE_DECLARE_DATA
+#else
+#define APU_MODULE_DECLARE_DATA __declspec(dllexport)
+#endif
+
+/*
+ * we always have SDBM (it's in our codebase)
+ */
+#define APU_HAVE_SDBM 1
+#define APU_HAVE_GDBM 0
+#define APU_HAVE_NDBM 0
+#define APU_HAVE_DB 0
+
+#if APU_HAVE_DB
+#define APU_HAVE_DB_VERSION 0
+#endif
+
+#define APU_HAVE_PGSQL 1
+#define APU_HAVE_MYSQL 0
+#define APU_HAVE_SQLITE3 1
+#define APU_HAVE_SQLITE2 0
+#define APU_HAVE_ORACLE 0
+#define APU_HAVE_FREETDS 0
+#define APU_HAVE_ODBC 0
+
+#define APU_HAVE_APR_ICONV 0
+#define APU_HAVE_ICONV 1
+#define APR_HAS_XLATE (APU_HAVE_APR_ICONV || APU_HAVE_ICONV)
+
+#endif /* APU_H */
+/** @} */
--- /dev/null
+/* 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 "apu.h" /* configuration data */
+
+/**
+ * @file apu_want.h
+ * @brief APR Standard Headers Support
+ *
+ * <PRE>
+ * Features:
+ *
+ * APU_WANT_DB: <db.h>
+ *
+ * Typical usage:
+ *
+ * #define APU_WANT_DB
+ * #include "apu_want.h"
+ *
+ * The appropriate headers will be included.
+ *
+ * Note: it is safe to use this in a header (it won't interfere with other
+ * headers' or source files' use of apu_want.h)
+ * </PRE>
+ */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef APU_WANT_DB
+
+#if APU_HAVE_DB
+#include <db.h>
+#endif
+
+#undef APU_WANT_DB
+#endif
+
+/* --------------------------------------------------------------------- */
--- /dev/null
+/* include/private/apu_config.h. Generated from apu_config.h.in by configure. */
+/* include/private/apu_config.h.in. Generated from configure.in by autoheader. */
+
+/* Define if the system crypt() function is threadsafe */
+/* #undef APU_CRYPT_THREADSAFE */
+
+/* Define to 1 if modular components are built as DSOs */
+#define APU_DSO_BUILD 1
+
+/* Define to be absolute path to DSO directory */
+#define APU_DSO_LIBDIR "/usr/local/apr/lib/apr-util-1"
+
+/* Define if the inbuf parm to iconv() is const char ** */
+/* #undef APU_ICONV_INBUF_CONST */
+
+/* Define if crypt_r has uses CRYPTD */
+/* #undef CRYPT_R_CRYPTD */
+
+/* Define if crypt_r uses struct crypt_data */
+#define CRYPT_R_STRUCT_CRYPT_DATA 1
+
+/* Define if CODESET is defined in langinfo.h */
+#define HAVE_CODESET 1
+
+/* Define to 1 if you have the `crypt_r' function. */
+#define HAVE_CRYPT_R 1
+
+/* Define if expat.h is available */
+#define HAVE_EXPAT_H 1
+
+/* Define to 1 if you have the <freetds/sybdb.h> header file. */
+/* #undef HAVE_FREETDS_SYBDB_H */
+
+/* Define to 1 if you have the <iconv.h> header file. */
+#define HAVE_ICONV_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Define to 1 if you have the <lber.h> header file. */
+/* #undef HAVE_LBER_H */
+
+/* Defined if ldap.h is present */
+/* #undef HAVE_LDAP_H */
+
+/* Define to 1 if you have the <ldap_ssl.h> header file. */
+/* #undef HAVE_LDAP_SSL_H */
+
+/* Define to 1 if you have the <libpq-fe.h> header file. */
+#define HAVE_LIBPQ_FE_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <mysql.h> header file. */
+/* #undef HAVE_MYSQL_H */
+
+/* Define to 1 if you have the <mysql/mysql.h> header file. */
+/* #undef HAVE_MYSQL_MYSQL_H */
+
+/* Define to 1 if you have the <mysql/my_global.h> header file. */
+/* #undef HAVE_MYSQL_MY_GLOBAL_H */
+
+/* Define to 1 if you have the <mysql/my_sys.h> header file. */
+/* #undef HAVE_MYSQL_MY_SYS_H */
+
+/* Define to 1 if you have the <my_global.h> header file. */
+/* #undef HAVE_MY_GLOBAL_H */
+
+/* Define to 1 if you have the <my_sys.h> header file. */
+/* #undef HAVE_MY_SYS_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#define HAVE_NL_LANGINFO 1
+
+/* Define to 1 if you have the <oci.h> header file. */
+/* #undef HAVE_OCI_H */
+
+/* Define to 1 if you have the <odbc/sql.h> header file. */
+/* #undef HAVE_ODBC_SQL_H */
+
+/* Define to 1 if you have the <postgresql/libpq-fe.h> header file. */
+/* #undef HAVE_POSTGRESQL_LIBPQ_FE_H */
+
+/* Define to 1 if you have the <sqlite3.h> header file. */
+#define HAVE_SQLITE3_H 1
+
+/* Define to 1 if you have the <sqlite.h> header file. */
+/* #undef HAVE_SQLITE_H */
+
+/* Define to 1 if you have the <sql.h> header file. */
+/* #undef HAVE_SQL_H */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sybdb.h> header file. */
+/* #undef HAVE_SYBDB_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define if xmlparse/xmlparse.h is available */
+/* #undef HAVE_XMLPARSE_XMLPARSE_H */
+
+/* Define if xmltok/xmlparse.h is available */
+/* #undef HAVE_XMLTOK_XMLPARSE_H */
+
+/* Define if xml/xmlparse.h is available */
+/* #undef HAVE_XML_XMLPARSE_H */
+
+/* Define if ldap_set_rebind_proc takes three arguments */
+/* #undef LDAP_SET_REBIND_PROC_THREE */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
--- /dev/null
+/* 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.
+ */
+
+#ifndef APU_SELECT_DBM_H
+#define APU_SELECT_DBM_H
+
+/*
+** The following macros control what features APRUTIL will use
+*/
+#define APU_USE_SDBM 1
+#define APU_USE_NDBM 0
+#define APU_USE_GDBM 0
+#define APU_USE_DB 0
+
+#endif /* !APU_SELECT_DBM_H */
--- /dev/null
+/* 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.
+ */
+
+/*
+ * apr_ldap.h is generated from apr_ldap.h.in by configure -- do not edit apr_ldap.h
+ */
+/**
+ * @file apr_ldap.h
+ * @brief APR-UTIL LDAP
+ */
+#ifndef APU_LDAP_H
+#define APU_LDAP_H
+
+/**
+ * @defgroup APR_Util_LDAP LDAP
+ * @ingroup APR_Util
+ * @{
+ */
+
+/* this will be defined if LDAP support was compiled into apr-util */
+#define APR_HAS_LDAP 0
+
+/* identify the LDAP toolkit used */
+#define APR_HAS_NETSCAPE_LDAPSDK 0
+#define APR_HAS_SOLARIS_LDAPSDK 0
+#define APR_HAS_NOVELL_LDAPSDK 0
+#define APR_HAS_MOZILLA_LDAPSDK 0
+#define APR_HAS_OPENLDAP_LDAPSDK 0
+#define APR_HAS_MICROSOFT_LDAPSDK 0
+#define APR_HAS_TIVOLI_LDAPSDK 0
+#define APR_HAS_ZOS_LDAPSDK 0
+#define APR_HAS_OTHER_LDAPSDK 0
+
+
+/*
+ * Handle the case when LDAP is enabled
+ */
+#if APR_HAS_LDAP
+
+/*
+ * The following #defines are DEPRECATED and should not be used for
+ * anything. They remain to maintain binary compatibility.
+ * The original code defined the OPENLDAP SDK as present regardless
+ * of what really was there, which was way bogus. In addition, the
+ * apr_ldap_url_parse*() functions have been rewritten specifically for
+ * APR, so the APR_HAS_LDAP_URL_PARSE macro is forced to zero.
+ */
+#if APR_HAS_TIVOLI_LDAPSDK
+#define APR_HAS_LDAP_SSL 0
+#else
+#define APR_HAS_LDAP_SSL 1
+#endif
+#define APR_HAS_LDAP_URL_PARSE 0
+
+#if APR_HAS_OPENLDAP_LDAPSDK && !defined(LDAP_DEPRECATED)
+/* Ensure that the "deprecated" interfaces are still exposed
+ * with OpenLDAP >= 2.3; these were exposed by default in earlier
+ * releases. */
+#define LDAP_DEPRECATED 1
+#endif
+
+/*
+ * Include the standard LDAP header files.
+ */
+
+
+
+
+
+
+/*
+ * Detected standard functions
+ */
+#define APR_HAS_LDAPSSL_CLIENT_INIT 0
+#define APR_HAS_LDAPSSL_CLIENT_DEINIT 0
+#define APR_HAS_LDAPSSL_ADD_TRUSTED_CERT 0
+#define APR_HAS_LDAP_START_TLS_S 0
+#define APR_HAS_LDAP_SSLINIT 0
+#define APR_HAS_LDAPSSL_INIT 0
+#define APR_HAS_LDAPSSL_INSTALL_ROUTINES 0
+
+/*
+ * Make sure the secure LDAP port is defined
+ */
+#ifndef LDAPS_PORT
+#define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
+#endif
+
+/*
+ * For ldap function calls that input a size limit on the number of returned elements
+ * Some SDKs do not have the define for LDAP_DEFAULT_LIMIT (-1) or LDAP_NO_LIMIT (0)
+ * LDAP_DEFAULT_LIMIT is preferred as it allows inheritance from whatever the SDK
+ * or process is configured for.
+ */
+#ifdef LDAP_DEFAULT_LIMIT
+#define APR_LDAP_SIZELIMIT LDAP_DEFAULT_LIMIT
+#else
+#ifdef LDAP_NO_LIMIT
+#define APR_LDAP_SIZELIMIT LDAP_NO_LIMIT
+#endif
+#endif
+
+#ifndef APR_LDAP_SIZELIMIT
+#define APR_LDAP_SIZELIMIT 0 /* equivalent to LDAP_NO_LIMIT, and what goes on the wire */
+#endif
+
+/*
+ * z/OS is missing some defines
+ */
+#ifndef LDAP_VERSION_MAX
+#define LDAP_VERSION_MAX LDAP_VERSION
+#endif
+#if APR_HAS_ZOS_LDAPSDK
+#define LDAP_VENDOR_NAME "IBM z/OS"
+#endif
+
+/* Note: Macros defining const casting has been removed in APR v1.0,
+ * pending real support for LDAP v2.0 toolkits.
+ *
+ * In the mean time, please use an LDAP v3.0 toolkit.
+ */
+#if LDAP_VERSION_MAX <= 2
+#error Support for LDAP v2.0 toolkits has been removed from apr-util. Please use an LDAP v3.0 toolkit.
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * This structure allows the C LDAP API error codes to be returned
+ * along with plain text error messages that explain to us mere mortals
+ * what really happened.
+ */
+typedef struct apr_ldap_err_t {
+ const char *reason;
+ const char *msg;
+ int rc;
+} apr_ldap_err_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* The MS SDK returns LDAP_UNAVAILABLE when the backend has closed the connection
+ * between LDAP calls. Protect with APR_HAS_MICROSOFT_LDAPSDK in case someone
+ * manually chooses another SDK on Windows
+ */
+#if APR_HAS_MICROSOFT_LDAPSDK
+#define APR_LDAP_IS_SERVER_DOWN(s) ((s) == LDAP_SERVER_DOWN \
+ || (s) == LDAP_UNAVAILABLE)
+#else
+#define APR_LDAP_IS_SERVER_DOWN(s) ((s) == LDAP_SERVER_DOWN)
+#endif
+
+/* These symbols are not actually exported in a DSO build, but mapped into
+ * a private exported function array for apr_ldap_stub to bind dynamically.
+ * Rename them appropriately to protect the global namespace.
+ */
+#ifdef APU_DSO_LDAP_BUILD
+
+#define apr_ldap_info apr__ldap_info
+#define apr_ldap_init apr__ldap_init
+#define apr_ldap_ssl_init apr__ldap_ssl_init
+#define apr_ldap_ssl_deinit apr__ldap_ssl_deinit
+#define apr_ldap_get_option apr__ldap_get_option
+#define apr_ldap_set_option apr__ldap_set_option
+#define apr_ldap_rebind_init apr__ldap_rebind_init
+#define apr_ldap_rebind_add apr__ldap_rebind_add
+#define apr_ldap_rebind_remove apr__ldap_rebind_remove
+
+#define APU_DECLARE_LDAP(type) type
+#else
+#define APU_DECLARE_LDAP(type) APU_DECLARE(type)
+#endif
+
+#include "apr_ldap_url.h"
+#include "apr_ldap_init.h"
+#include "apr_ldap_option.h"
+#include "apr_ldap_rebind.h"
+
+/** @} */
+#endif /* APR_HAS_LDAP */
+#endif /* APU_LDAP_H */
--- /dev/null
+/* 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.
+ */
+
+/*
+ * apu.h is generated from apu.h.in by configure -- do not edit apu.h
+ */
+/* @file apu.h
+ * @brief APR-Utility main file
+ */
+/**
+ * @defgroup APR_Util APR Utility Functions
+ * @{
+ */
+
+
+#ifndef APU_H
+#define APU_H
+
+/**
+ * APU_DECLARE_EXPORT is defined when building the APR-UTIL dynamic library,
+ * so that all public symbols are exported.
+ *
+ * APU_DECLARE_STATIC is defined when including the APR-UTIL public headers,
+ * to provide static linkage when the dynamic library may be unavailable.
+ *
+ * APU_DECLARE_STATIC and APU_DECLARE_EXPORT are left undefined when
+ * including the APR-UTIL public headers, to import and link the symbols from
+ * the dynamic APR-UTIL library and assure appropriate indirection and calling
+ * conventions at compile time.
+ */
+
+/**
+ * The public APR-UTIL functions are declared with APU_DECLARE(), so they may
+ * use the most appropriate calling convention. Public APR functions with
+ * variable arguments must use APU_DECLARE_NONSTD().
+ *
+ * @fn APU_DECLARE(rettype) apr_func(args);
+ */
+#define APU_DECLARE(type) type
+/**
+ * The public APR-UTIL functions using variable arguments are declared with
+ * APU_DECLARE_NONSTD(), as they must use the C language calling convention.
+ *
+ * @fn APU_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ */
+#define APU_DECLARE_NONSTD(type) type
+/**
+ * The public APR-UTIL variables are declared with APU_DECLARE_DATA.
+ * This assures the appropriate indirection is invoked at compile time.
+ *
+ * @fn APU_DECLARE_DATA type apr_variable;
+ * @note APU_DECLARE_DATA extern type apr_variable; syntax is required for
+ * declarations within headers to properly import the variable.
+ */
+#define APU_DECLARE_DATA
+
+#if !defined(WIN32) || defined(APU_MODULE_DECLARE_STATIC)
+/**
+ * Declare a dso module's exported module structure as APU_MODULE_DECLARE_DATA.
+ *
+ * Unless APU_MODULE_DECLARE_STATIC is defined at compile time, symbols
+ * declared with APU_MODULE_DECLARE_DATA are always exported.
+ * @code
+ * module APU_MODULE_DECLARE_DATA mod_tag
+ * @endcode
+ */
+#define APU_MODULE_DECLARE_DATA
+#else
+#define APU_MODULE_DECLARE_DATA __declspec(dllexport)
+#endif
+
+/*
+ * we always have SDBM (it's in our codebase)
+ */
+#define APU_HAVE_SDBM 1
+#define APU_HAVE_GDBM 0
+#define APU_HAVE_NDBM 0
+#define APU_HAVE_DB 0
+
+#if APU_HAVE_DB
+#define APU_HAVE_DB_VERSION 0
+#endif
+
+#define APU_HAVE_PGSQL 0
+#define APU_HAVE_MYSQL 0
+#define APU_HAVE_SQLITE3 1
+#define APU_HAVE_SQLITE2 0
+#define APU_HAVE_ORACLE 0
+#define APU_HAVE_FREETDS 0
+#define APU_HAVE_ODBC 0
+
+#define APU_HAVE_APR_ICONV 0
+#define APU_HAVE_ICONV 1
+#define APR_HAS_XLATE (APU_HAVE_APR_ICONV || APU_HAVE_ICONV)
+
+#endif /* APU_H */
+/** @} */
--- /dev/null
+/* 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 "apu.h" /* configuration data */
+
+/**
+ * @file apu_want.h
+ * @brief APR Standard Headers Support
+ *
+ * <PRE>
+ * Features:
+ *
+ * APU_WANT_DB: <db.h>
+ *
+ * Typical usage:
+ *
+ * #define APU_WANT_DB
+ * #include "apu_want.h"
+ *
+ * The appropriate headers will be included.
+ *
+ * Note: it is safe to use this in a header (it won't interfere with other
+ * headers' or source files' use of apu_want.h)
+ * </PRE>
+ */
+
+/* --------------------------------------------------------------------- */
+
+#ifdef APU_WANT_DB
+
+#if APU_HAVE_DB
+#include <db.h>
+#endif
+
+#undef APU_WANT_DB
+#endif
+
+/* --------------------------------------------------------------------- */
--- /dev/null
+/* include/private/apu_config.h. Generated from apu_config.h.in by configure. */
+/* include/private/apu_config.h.in. Generated from configure.in by autoheader. */
+
+/* Define if the system crypt() function is threadsafe */
+/* #undef APU_CRYPT_THREADSAFE */
+
+/* Define to 1 if modular components are built as DSOs */
+#define APU_DSO_BUILD 1
+
+/* Define to be absolute path to DSO directory */
+#define APU_DSO_LIBDIR "/usr/local/apr/lib/apr-util-1"
+
+/* Define if the inbuf parm to iconv() is const char ** */
+/* #undef APU_ICONV_INBUF_CONST */
+
+/* Define if crypt_r has uses CRYPTD */
+/* #undef CRYPT_R_CRYPTD */
+
+/* Define if crypt_r uses struct crypt_data */
+/* #undef CRYPT_R_STRUCT_CRYPT_DATA */
+
+/* Define if CODESET is defined in langinfo.h */
+#define HAVE_CODESET 1
+
+/* Define to 1 if you have the `crypt_r' function. */
+/* #undef HAVE_CRYPT_R */
+
+/* Define if expat.h is available */
+#define HAVE_EXPAT_H 1
+
+/* Define to 1 if you have the <freetds/sybdb.h> header file. */
+/* #undef HAVE_FREETDS_SYBDB_H */
+
+/* Define to 1 if you have the <iconv.h> header file. */
+#define HAVE_ICONV_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Define to 1 if you have the <lber.h> header file. */
+/* #undef HAVE_LBER_H */
+
+/* Defined if ldap.h is present */
+/* #undef HAVE_LDAP_H */
+
+/* Define to 1 if you have the <ldap_ssl.h> header file. */
+/* #undef HAVE_LDAP_SSL_H */
+
+/* Define to 1 if you have the <libpq-fe.h> header file. */
+/* #undef HAVE_LIBPQ_FE_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <mysql.h> header file. */
+/* #undef HAVE_MYSQL_H */
+
+/* Define to 1 if you have the <mysql/mysql.h> header file. */
+/* #undef HAVE_MYSQL_MYSQL_H */
+
+/* Define to 1 if you have the <mysql/my_global.h> header file. */
+/* #undef HAVE_MYSQL_MY_GLOBAL_H */
+
+/* Define to 1 if you have the <mysql/my_sys.h> header file. */
+/* #undef HAVE_MYSQL_MY_SYS_H */
+
+/* Define to 1 if you have the <my_global.h> header file. */
+/* #undef HAVE_MY_GLOBAL_H */
+
+/* Define to 1 if you have the <my_sys.h> header file. */
+/* #undef HAVE_MY_SYS_H */
+
+/* Define to 1 if you have the `nl_langinfo' function. */
+#define HAVE_NL_LANGINFO 1
+
+/* Define to 1 if you have the <oci.h> header file. */
+/* #undef HAVE_OCI_H */
+
+/* Define to 1 if you have the <odbc/sql.h> header file. */
+/* #undef HAVE_ODBC_SQL_H */
+
+/* Define to 1 if you have the <postgresql/libpq-fe.h> header file. */
+/* #undef HAVE_POSTGRESQL_LIBPQ_FE_H */
+
+/* Define to 1 if you have the <sqlite3.h> header file. */
+#define HAVE_SQLITE3_H 1
+
+/* Define to 1 if you have the <sqlite.h> header file. */
+/* #undef HAVE_SQLITE_H */
+
+/* Define to 1 if you have the <sql.h> header file. */
+#define HAVE_SQL_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sybdb.h> header file. */
+/* #undef HAVE_SYBDB_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define if xmlparse/xmlparse.h is available */
+/* #undef HAVE_XMLPARSE_XMLPARSE_H */
+
+/* Define if xmltok/xmlparse.h is available */
+/* #undef HAVE_XMLTOK_XMLPARSE_H */
+
+/* Define if xml/xmlparse.h is available */
+/* #undef HAVE_XML_XMLPARSE_H */
+
+/* Define if ldap_set_rebind_proc takes three arguments */
+/* #undef LDAP_SET_REBIND_PROC_THREE */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
--- /dev/null
+/* 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.
+ */
+
+#ifndef APU_SELECT_DBM_H
+#define APU_SELECT_DBM_H
+
+/*
+** The following macros control what features APRUTIL will use
+*/
+#define APU_USE_SDBM 1
+#define APU_USE_NDBM 0
+#define APU_USE_GDBM 0
+#define APU_USE_DB 0
+
+#endif /* !APU_SELECT_DBM_H */
--- /dev/null
+/* include/ap_config_auto.h. Generated from ap_config_auto.h.in by configure. */
+/* include/ap_config_auto.h.in. Generated from configure.in by autoheader. */
+
+/* Location of the source for the current MPM */
+#define APACHE_MPM_DIR "server/mpm/prefork"
+
+/* SuExec root directory */
+/* #undef AP_DOC_ROOT */
+
+/* Allow modules to run hook after a fatal exception */
+/* #undef AP_ENABLE_EXCEPTION_HOOK */
+
+/* Allow IPv4 connections on IPv6 listening sockets */
+#define AP_ENABLE_V4_MAPPED 1
+
+/* Minimum allowed GID */
+/* #undef AP_GID_MIN */
+
+/* User allowed to call SuExec */
+/* #undef AP_HTTPD_USER */
+
+/* SuExec log file */
+/* #undef AP_LOG_EXEC */
+
+/* Listening sockets are non-blocking when there are more than 1 */
+#define AP_NONBLOCK_WHEN_MULTI_LISTEN 1
+
+/* safe shell path for SuExec */
+/* #undef AP_SAFE_PATH */
+
+/* umask for suexec'd process */
+/* #undef AP_SUEXEC_UMASK */
+
+/* Location of the MIME types config file, relative to the Apache root
+ directory */
+#define AP_TYPES_CONFIG_FILE "conf/mime.types"
+
+/* Minimum allowed UID */
+/* #undef AP_UID_MIN */
+
+/* User subdirectory */
+/* #undef AP_USERDIR_SUFFIX */
+
+/* Using autoconf to configure Apache */
+#define AP_USING_AUTOCONF 1
+
+/* Define to 1 if you have the `bindprocessor' function. */
+/* #undef HAVE_BINDPROCESSOR */
+
+/* Define to 1 if you have the <bstring.h> header file. */
+/* #undef HAVE_BSTRING_H */
+
+/* Define if distcache support is enabled */
+/* #undef HAVE_DISTCACHE */
+
+/* Define to 1 if you have the `ENGINE_init' function. */
+/* #undef HAVE_ENGINE_INIT */
+
+/* Define to 1 if you have the `ENGINE_load_builtin_engines' function. */
+/* #undef HAVE_ENGINE_LOAD_BUILTIN_ENGINES */
+
+/* Define to 1 if you have the `getgrnam' function. */
+#define HAVE_GETGRNAM 1
+
+/* Define to 1 if you have the `getpgid' function. */
+#define HAVE_GETPGID 1
+
+/* Define to 1 if you have the `getpwnam' function. */
+#define HAVE_GETPWNAM 1
+
+/* Define if struct tm has a tm_gmtoff field */
+#define HAVE_GMTOFF 1
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define to 1 if you have the `initgroups' function. */
+#define HAVE_INITGROUPS 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `killpg' function. */
+#define HAVE_KILLPG 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define if SSL is supported using OpenSSL */
+/* #undef HAVE_OPENSSL */
+
+/* Define to 1 if you have the <openssl/engine.h> header file. */
+/* #undef HAVE_OPENSSL_ENGINE_H */
+
+/* Define to 1 if you have the <openssl/opensslv.h> header file. */
+/* #undef HAVE_OPENSSL_OPENSSLV_H */
+
+/* Define to 1 if you have the <openssl/ssl.h> header file. */
+/* #undef HAVE_OPENSSL_SSL_H */
+
+/* Define to 1 if you have the `prctl' function. */
+#define HAVE_PRCTL 1
+
+/* Define to 1 if you have the `pthread_kill' function. */
+/* #undef HAVE_PTHREAD_KILL */
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define if SSL is supported using SSL-C */
+/* #undef HAVE_SSLC */
+
+/* Define to 1 if you have the <sslc.h> header file. */
+/* #undef HAVE_SSLC_H */
+
+/* Define to 1 if you have the `SSLC_library_version' function. */
+/* #undef HAVE_SSLC_LIBRARY_VERSION */
+
+/* Define to 1 if you have the `SSLeay_version' function. */
+/* #undef HAVE_SSLEAY_VERSION */
+
+/* Define to 1 if you have the `SSL_CTX_new' function. */
+/* #undef HAVE_SSL_CTX_NEW */
+
+/* Define to 1 if you have the `SSL_set_cert_store' function. */
+/* #undef HAVE_SSL_SET_CERT_STORE */
+
+/* Define to 1 if you have the `SSL_set_state' function. */
+/* #undef HAVE_SSL_SET_STATE */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `syslog' function. */
+/* #undef HAVE_SYSLOG */
+
+/* Define to 1 if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define to 1 if you have the <sys/prctl.h> header file. */
+#define HAVE_SYS_PRCTL_H 1
+
+/* Define to 1 if you have the <sys/processor.h> header file. */
+/* #undef HAVE_SYS_PROCESSOR_H */
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/sem.h> header file. */
+#define HAVE_SYS_SEM_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/times.h> header file. */
+#define HAVE_SYS_TIMES_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the `timegm' function. */
+#define HAVE_TIMEGM 1
+
+/* Define to 1 if you have the `times' function. */
+#define HAVE_TIMES 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Root directory of the Apache install area */
+/* #define HTTPD_ROOT "/usr/local/apache2" */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Location of the config file, relative to the Apache root directory */
+#define SERVER_CONFIG_FILE "conf/httpd.conf"
+
+/* This platform doesn't suffer from the thundering herd problem */
+#define SINGLE_LISTEN_UNSERIALIZED_ACCEPT 1
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Path to suexec binary */
+/* #undef SUEXEC_BIN */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Enable extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to 'int' if <sys/resource.h> doesn't define it for us */
+/* #undef rlim_t */
--- /dev/null
+/* 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.
+ */
+
+/**
+ * @file ap_config_layout.h
+ * @brief Apache Config Layout
+ */
+
+#ifndef AP_CONFIG_LAYOUT_H
+#define AP_CONFIG_LAYOUT_H
+
+/* Configured Apache directory layout */
+/*
+#define DEFAULT_PREFIX "/usr/local/apache2"
+#define DEFAULT_EXP_EXEC_PREFIX "/usr/local/apache2"
+#define DEFAULT_REL_EXEC_PREFIX ""
+#define DEFAULT_EXP_BINDIR "/usr/local/apache2/bin"
+#define DEFAULT_REL_BINDIR "bin"
+#define DEFAULT_EXP_SBINDIR "/usr/local/apache2/bin"
+#define DEFAULT_REL_SBINDIR "bin"
+#define DEFAULT_EXP_LIBEXECDIR "/usr/local/apache2/modules"
+#define DEFAULT_REL_LIBEXECDIR "modules"
+#define DEFAULT_EXP_MANDIR "/usr/local/apache2/man"
+#define DEFAULT_REL_MANDIR "man"
+#define DEFAULT_EXP_SYSCONFDIR "/usr/local/apache2/conf"
+#define DEFAULT_REL_SYSCONFDIR "conf"
+#define DEFAULT_EXP_DATADIR "/usr/local/apache2"
+#define DEFAULT_REL_DATADIR ""
+#define DEFAULT_EXP_INSTALLBUILDDIR "/usr/local/apache2/build"
+#define DEFAULT_REL_INSTALLBUILDDIR "build"
+#define DEFAULT_EXP_ERRORDIR "/usr/local/apache2/error"
+#define DEFAULT_REL_ERRORDIR "error"
+#define DEFAULT_EXP_ICONSDIR "/usr/local/apache2/icons"
+#define DEFAULT_REL_ICONSDIR "icons"
+#define DEFAULT_EXP_HTDOCSDIR "/usr/local/apache2/htdocs"
+#define DEFAULT_REL_HTDOCSDIR "htdocs"
+#define DEFAULT_EXP_MANUALDIR "/usr/local/apache2/manual"
+#define DEFAULT_REL_MANUALDIR "manual"
+#define DEFAULT_EXP_CGIDIR "/usr/local/apache2/cgi-bin"
+#define DEFAULT_REL_CGIDIR "cgi-bin"
+#define DEFAULT_EXP_INCLUDEDIR "/usr/local/apache2/include"
+#define DEFAULT_REL_INCLUDEDIR "include"
+#define DEFAULT_EXP_LOCALSTATEDIR "/usr/local/apache2"
+#define DEFAULT_REL_LOCALSTATEDIR ""
+#define DEFAULT_EXP_RUNTIMEDIR "/usr/local/apache2/logs"
+#define DEFAULT_REL_RUNTIMEDIR "logs"
+#define DEFAULT_EXP_LOGFILEDIR "/usr/local/apache2/logs"
+#define DEFAULT_REL_LOGFILEDIR "logs"
+#define DEFAULT_EXP_PROXYCACHEDIR "/usr/local/apache2/proxy"
+#define DEFAULT_REL_PROXYCACHEDIR "proxy"
+*/
+
+#endif /* AP_CONFIG_LAYOUT_H */
--- /dev/null
+/* include/ap_config_auto.h. Generated automatically by configure. */
+/* include/ap_config_auto.h.in. Generated automatically from configure.in by autoheader 2.13. */
+
+/* Define if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* #undef _ALL_SOURCE */
+#endif
+
+/* Define to empty if the keyword does not work. */
+/* #undef const */
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define if on MINIX. */
+/* #undef _MINIX */
+
+/* Define if the system does not provide POSIX.1 features except
+ with this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define if you need to in order for stat and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if you have the ENGINE_init function. */
+/* #undef HAVE_ENGINE_INIT */
+
+/* Define if you have the ENGINE_load_builtin_engines function. */
+/* #undef HAVE_ENGINE_LOAD_BUILTIN_ENGINES */
+
+/* Define if you have the SSLC_library_version function. */
+/* #undef HAVE_SSLC_LIBRARY_VERSION */
+
+/* Define if you have the SSL_CTX_new function. */
+/* #undef HAVE_SSL_CTX_NEW */
+
+/* Define if you have the SSL_set_cert_store function. */
+/* #undef HAVE_SSL_SET_CERT_STORE */
+
+/* Define if you have the SSL_set_state function. */
+/* #undef HAVE_SSL_SET_STATE */
+
+/* Define if you have the SSLeay_version function. */
+/* #undef HAVE_SSLEAY_VERSION */
+
+/* Define if you have the bindprocessor function. */
+/* #undef HAVE_BINDPROCESSOR */
+
+/* Define if you have the getgrnam function. */
+#define HAVE_GETGRNAM 1
+
+/* Define if you have the getpgid function. */
+#define HAVE_GETPGID 1
+
+/* Define if you have the getpwnam function. */
+#define HAVE_GETPWNAM 1
+
+/* Define if you have the initgroups function. */
+#define HAVE_INITGROUPS 1
+
+/* Define if you have the killpg function. */
+#define HAVE_KILLPG 1
+
+/* Define if you have the prctl function. */
+#define HAVE_PRCTL 1
+
+/* Define if you have the pthread_kill function. */
+/* #undef HAVE_PTHREAD_KILL */
+
+/* Define if you have the setsid function. */
+#define HAVE_SETSID 1
+
+/* Define if you have the syslog function. */
+/* #undef HAVE_SYSLOG */
+
+/* Define if you have the timegm function. */
+#define HAVE_TIMEGM 1
+
+/* Define if you have the times function. */
+#define HAVE_TIMES 1
+
+/* Define if you have the <bstring.h> header file. */
+/* #undef HAVE_BSTRING_H */
+
+/* Define if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define if you have the <openssl/engine.h> header file. */
+/* #undef HAVE_OPENSSL_ENGINE_H */
+
+/* Define if you have the <openssl/opensslv.h> header file. */
+/* #undef HAVE_OPENSSL_OPENSSLV_H */
+
+/* Define if you have the <openssl/ssl.h> header file. */
+/* #undef HAVE_OPENSSL_SSL_H */
+
+/* Define if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define if you have the <sslc.h> header file. */
+/* #undef HAVE_SSLC_H */
+
+/* Define if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define if you have the <sys/prctl.h> header file. */
+#define HAVE_SYS_PRCTL_H 1
+
+/* Define if you have the <sys/processor.h> header file. */
+/* #undef HAVE_SYS_PROCESSOR_H */
+
+/* Define if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define if you have the <sys/sem.h> header file. */
+#define HAVE_SYS_SEM_H 1
+
+/* Define if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define if you have the <sys/times.h> header file. */
+#define HAVE_SYS_TIMES_H 1
+
+/* Define if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define if struct tm has a tm_gmtoff field */
+#define HAVE_GMTOFF 1
+
+/* Allow IPv4 connections on IPv6 listening sockets */
+#define AP_ENABLE_V4_MAPPED 1
+
+/* Allow modules to run hook after a fatal exception */
+/* #undef AP_ENABLE_EXCEPTION_HOOK */
+
+/* Define if SSL is supported using OpenSSL */
+/* #undef HAVE_OPENSSL */
+
+/* Define if SSL is supported using SSL-C */
+/* #undef HAVE_SSLC */
+
+/* Define if distcache support is enabled */
+/* #undef HAVE_DISTCACHE */
+
+/* Define to 'int' if <sys/resource.h> doesn't define it for us */
+/* #undef rlim_t */
+
+/* Path to suexec binary */
+/* #undef SUEXEC_BIN */
+
+/* User allowed to call SuExec */
+/* #undef AP_HTTPD_USER */
+
+/* User subdirectory */
+/* #undef AP_USERDIR_SUFFIX */
+
+/* SuExec root directory */
+/* #undef AP_DOC_ROOT */
+
+/* Minimum allowed UID */
+/* #undef AP_UID_MIN */
+
+/* Minimum allowed GID */
+/* #undef AP_GID_MIN */
+
+/* SuExec log file */
+/* #undef AP_LOG_EXEC */
+
+/* safe shell path for SuExec */
+/* #undef AP_SAFE_PATH */
+
+/* umask for suexec'd process */
+/* #undef AP_SUEXEC_UMASK */
+
+/* Using autoconf to configure Apache */
+#define AP_USING_AUTOCONF 1
+
+/* This platform doesn't suffer from the thundering herd problem */
+#define SINGLE_LISTEN_UNSERIALIZED_ACCEPT 1
+
+/* Listening sockets are non-blocking when there are more than 1 */
+#define AP_NONBLOCK_WHEN_MULTI_LISTEN 1
+
+/* Root directory of the Apache install area */
+/* #define HTTPD_ROOT "/usr/local/apache2" */
+
+/* Location of the config file, relative to the Apache root directory */
+#define SERVER_CONFIG_FILE "conf/httpd.conf"
+
+/* Location of the MIME types config file, relative to the Apache root directory */
+#define AP_TYPES_CONFIG_FILE "conf/mime.types"
+
+/* Location of the source for the current MPM */
+#define APACHE_MPM_DIR "server/mpm/prefork"
+
--- /dev/null
+/* 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.
+ */
+
+/**
+ * @file ap_config_layout.h
+ * @brief Apache Config Layout
+ */
+
+#ifndef AP_CONFIG_LAYOUT_H
+#define AP_CONFIG_LAYOUT_H
+
+/* Configured Apache directory layout */
+/*
+#define DEFAULT_PREFIX "/usr/local/apache2"
+#define DEFAULT_EXP_EXEC_PREFIX "/usr/local/apache2"
+#define DEFAULT_REL_EXEC_PREFIX ""
+#define DEFAULT_EXP_BINDIR "/usr/local/apache2/bin"
+#define DEFAULT_REL_BINDIR "bin"
+#define DEFAULT_EXP_SBINDIR "/usr/local/apache2/bin"
+#define DEFAULT_REL_SBINDIR "bin"
+#define DEFAULT_EXP_LIBEXECDIR "/usr/local/apache2/modules"
+#define DEFAULT_REL_LIBEXECDIR "modules"
+#define DEFAULT_EXP_MANDIR "/usr/local/apache2/man"
+#define DEFAULT_REL_MANDIR "man"
+#define DEFAULT_EXP_SYSCONFDIR "/usr/local/apache2/conf"
+#define DEFAULT_REL_SYSCONFDIR "conf"
+#define DEFAULT_EXP_DATADIR "/usr/local/apache2"
+#define DEFAULT_REL_DATADIR ""
+#define DEFAULT_EXP_INSTALLBUILDDIR "/usr/local/apache2/build"
+#define DEFAULT_REL_INSTALLBUILDDIR "build"
+#define DEFAULT_EXP_ERRORDIR "/usr/local/apache2/error"
+#define DEFAULT_REL_ERRORDIR "error"
+#define DEFAULT_EXP_ICONSDIR "/usr/local/apache2/icons"
+#define DEFAULT_REL_ICONSDIR "icons"
+#define DEFAULT_EXP_HTDOCSDIR "/usr/local/apache2/htdocs"
+#define DEFAULT_REL_HTDOCSDIR "htdocs"
+#define DEFAULT_EXP_MANUALDIR "/usr/local/apache2/manual"
+#define DEFAULT_REL_MANUALDIR "manual"
+#define DEFAULT_EXP_CGIDIR "/usr/local/apache2/cgi-bin"
+#define DEFAULT_REL_CGIDIR "cgi-bin"
+#define DEFAULT_EXP_INCLUDEDIR "/usr/local/apache2/include"
+#define DEFAULT_REL_INCLUDEDIR "include"
+#define DEFAULT_EXP_LOCALSTATEDIR "/usr/local/apache2"
+#define DEFAULT_REL_LOCALSTATEDIR ""
+#define DEFAULT_EXP_RUNTIMEDIR "/usr/local/apache2/logs"
+#define DEFAULT_REL_RUNTIMEDIR "logs"
+#define DEFAULT_EXP_LOGFILEDIR "/usr/local/apache2/logs"
+#define DEFAULT_REL_LOGFILEDIR "logs"
+#define DEFAULT_EXP_PROXYCACHEDIR "/usr/local/apache2/proxy"
+#define DEFAULT_REL_PROXYCACHEDIR "proxy"
+*/
+
+#endif /* AP_CONFIG_LAYOUT_H */
--- /dev/null
+/* include/ap_config_auto.h. Generated from ap_config_auto.h.in by configure. */
+/* include/ap_config_auto.h.in. Generated from configure.in by autoheader. */
+
+/* Location of the source for the current MPM */
+#define APACHE_MPM_DIR "server/mpm/prefork"
+
+/* SuExec root directory */
+/* #undef AP_DOC_ROOT */
+
+/* Allow modules to run hook after a fatal exception */
+/* #undef AP_ENABLE_EXCEPTION_HOOK */
+
+/* Allow IPv4 connections on IPv6 listening sockets */
+#define AP_ENABLE_V4_MAPPED 1
+
+/* Minimum allowed GID */
+/* #undef AP_GID_MIN */
+
+/* User allowed to call SuExec */
+/* #undef AP_HTTPD_USER */
+
+/* SuExec log file */
+/* #undef AP_LOG_EXEC */
+
+/* Listening sockets are non-blocking when there are more than 1 */
+#define AP_NONBLOCK_WHEN_MULTI_LISTEN 1
+
+/* safe shell path for SuExec */
+/* #undef AP_SAFE_PATH */
+
+/* umask for suexec'd process */
+/* #undef AP_SUEXEC_UMASK */
+
+/* Location of the MIME types config file, relative to the Apache root
+ directory */
+#define AP_TYPES_CONFIG_FILE "conf/mime.types"
+
+/* Minimum allowed UID */
+/* #undef AP_UID_MIN */
+
+/* User subdirectory */
+/* #undef AP_USERDIR_SUFFIX */
+
+/* Using autoconf to configure Apache */
+#define AP_USING_AUTOCONF 1
+
+/* Define to 1 if you have the `bindprocessor' function. */
+/* #undef HAVE_BINDPROCESSOR */
+
+/* Define to 1 if you have the <bstring.h> header file. */
+/* #undef HAVE_BSTRING_H */
+
+/* Define if distcache support is enabled */
+/* #undef HAVE_DISTCACHE */
+
+/* Define to 1 if you have the `ENGINE_init' function. */
+/* #undef HAVE_ENGINE_INIT */
+
+/* Define to 1 if you have the `ENGINE_load_builtin_engines' function. */
+/* #undef HAVE_ENGINE_LOAD_BUILTIN_ENGINES */
+
+/* Define to 1 if you have the `getgrnam' function. */
+#define HAVE_GETGRNAM 1
+
+/* Define to 1 if you have the `getpgid' function. */
+#define HAVE_GETPGID 1
+
+/* Define to 1 if you have the `getpwnam' function. */
+#define HAVE_GETPWNAM 1
+
+/* Define if struct tm has a tm_gmtoff field */
+#define HAVE_GMTOFF 1
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define to 1 if you have the `initgroups' function. */
+#define HAVE_INITGROUPS 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `killpg' function. */
+#define HAVE_KILLPG 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define if SSL is supported using OpenSSL */
+/* #undef HAVE_OPENSSL */
+
+/* Define to 1 if you have the <openssl/engine.h> header file. */
+/* #undef HAVE_OPENSSL_ENGINE_H */
+
+/* Define to 1 if you have the <openssl/opensslv.h> header file. */
+/* #undef HAVE_OPENSSL_OPENSSLV_H */
+
+/* Define to 1 if you have the <openssl/ssl.h> header file. */
+/* #undef HAVE_OPENSSL_SSL_H */
+
+/* Define to 1 if you have the `prctl' function. */
+/* #undef HAVE_PRCTL */
+
+/* Define to 1 if you have the `pthread_kill' function. */
+/* #undef HAVE_PTHREAD_KILL */
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define if SSL is supported using SSL-C */
+/* #undef HAVE_SSLC */
+
+/* Define to 1 if you have the <sslc.h> header file. */
+/* #undef HAVE_SSLC_H */
+
+/* Define to 1 if you have the `SSLC_library_version' function. */
+/* #undef HAVE_SSLC_LIBRARY_VERSION */
+
+/* Define to 1 if you have the `SSLeay_version' function. */
+/* #undef HAVE_SSLEAY_VERSION */
+
+/* Define to 1 if you have the `SSL_CTX_new' function. */
+/* #undef HAVE_SSL_CTX_NEW */
+
+/* Define to 1 if you have the `SSL_set_cert_store' function. */
+/* #undef HAVE_SSL_SET_CERT_STORE */
+
+/* Define to 1 if you have the `SSL_set_state' function. */
+/* #undef HAVE_SSL_SET_STATE */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `syslog' function. */
+/* #undef HAVE_SYSLOG */
+
+/* Define to 1 if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define to 1 if you have the <sys/prctl.h> header file. */
+/* #undef HAVE_SYS_PRCTL_H */
+
+/* Define to 1 if you have the <sys/processor.h> header file. */
+/* #undef HAVE_SYS_PROCESSOR_H */
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/sem.h> header file. */
+#define HAVE_SYS_SEM_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/times.h> header file. */
+#define HAVE_SYS_TIMES_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the `timegm' function. */
+#define HAVE_TIMEGM 1
+
+/* Define to 1 if you have the `times' function. */
+#define HAVE_TIMES 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Root directory of the Apache install area */
+/* #define HTTPD_ROOT "/Users/username/apache2" */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Location of the config file, relative to the Apache root directory */
+#define SERVER_CONFIG_FILE "conf/httpd.conf"
+
+/* This platform doesn't suffer from the thundering herd problem */
+#define SINGLE_LISTEN_UNSERIALIZED_ACCEPT 1
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Path to suexec binary */
+/* #undef SUEXEC_BIN */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to 'int' if <sys/resource.h> doesn't define it for us */
+/* #undef rlim_t */
--- /dev/null
+/* 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.
+ */
+
+/**
+ * @file ap_config_layout.h
+ * @brief Apache Config Layout
+ */
+
+#ifndef AP_CONFIG_LAYOUT_H
+#define AP_CONFIG_LAYOUT_H
+
+/* Configured Apache directory layout */
+/*
+#define DEFAULT_PREFIX "/Users/username/apache2"
+#define DEFAULT_EXP_EXEC_PREFIX "/Users/username/apache2"
+#define DEFAULT_REL_EXEC_PREFIX ""
+#define DEFAULT_EXP_BINDIR "/Users/username/apache2/bin"
+#define DEFAULT_REL_BINDIR "bin"
+#define DEFAULT_EXP_SBINDIR "/Users/username/apache2/bin"
+#define DEFAULT_REL_SBINDIR "bin"
+#define DEFAULT_EXP_LIBEXECDIR "/Users/username/apache2/modules"
+#define DEFAULT_REL_LIBEXECDIR "modules"
+#define DEFAULT_EXP_MANDIR "/Users/username/apache2/man"
+#define DEFAULT_REL_MANDIR "man"
+#define DEFAULT_EXP_SYSCONFDIR "/Users/username/apache2/conf"
+#define DEFAULT_REL_SYSCONFDIR "conf"
+#define DEFAULT_EXP_DATADIR "/Users/username/apache2"
+#define DEFAULT_REL_DATADIR ""
+#define DEFAULT_EXP_INSTALLBUILDDIR "/Users/username/apache2/build"
+#define DEFAULT_REL_INSTALLBUILDDIR "build"
+#define DEFAULT_EXP_ERRORDIR "/Users/username/apache2/error"
+#define DEFAULT_REL_ERRORDIR "error"
+#define DEFAULT_EXP_ICONSDIR "/Users/username/apache2/icons"
+#define DEFAULT_REL_ICONSDIR "icons"
+#define DEFAULT_EXP_HTDOCSDIR "/Users/username/apache2/htdocs"
+#define DEFAULT_REL_HTDOCSDIR "htdocs"
+#define DEFAULT_EXP_MANUALDIR "/Users/username/apache2/manual"
+#define DEFAULT_REL_MANUALDIR "manual"
+#define DEFAULT_EXP_CGIDIR "/Users/username/apache2/cgi-bin"
+#define DEFAULT_REL_CGIDIR "cgi-bin"
+#define DEFAULT_EXP_INCLUDEDIR "/Users/username/apache2/include"
+#define DEFAULT_REL_INCLUDEDIR "include"
+#define DEFAULT_EXP_LOCALSTATEDIR "/Users/username/apache2"
+#define DEFAULT_REL_LOCALSTATEDIR ""
+#define DEFAULT_EXP_RUNTIMEDIR "/Users/username/apache2/logs"
+#define DEFAULT_REL_RUNTIMEDIR "logs"
+#define DEFAULT_EXP_LOGFILEDIR "/Users/username/apache2/logs"
+#define DEFAULT_REL_LOGFILEDIR "logs"
+#define DEFAULT_EXP_PROXYCACHEDIR "/Users/username/apache2/proxy"
+#define DEFAULT_REL_PROXYCACHEDIR "proxy"
+*/
+
+#endif /* AP_CONFIG_LAYOUT_H */
--- /dev/null
+# Copyright 2010 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.
+
+{
+ 'variables': {
+ 'apache_root': '<(DEPTH)/third_party/apache/httpd',
+ 'apache_src_root': '<(apache_root)/src',
+ 'apache_gen_os_root': '<(apache_root)/gen/arch/<(OS)',
+ 'apache_gen_arch_root': '<(apache_gen_os_root)/<(target_arch)',
+ 'system_include_path_httpd%': '/usr/include/apache2',
+ 'conditions': [
+ ['OS!="win"', {
+ 'apache_os_include': '<(apache_src_root)/os/unix',
+ }, { # else, OS=="win"
+ 'apache_os_include': '<(apache_src_root)/os/win32',
+ }]
+ ],
+ },
+ 'conditions': [
+ ['use_system_apache_dev==0', {
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(apache_src_root)/include',
+ '<(apache_os_include)',
+ '<(apache_gen_arch_root)/include',
+ ],
+ },
+ 'dependencies': [
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:include',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:include',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:include',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:include',
+ ],
+ },
+ ],
+ }, {
+ 'targets': [
+ {
+ 'target_name': 'include',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(system_include_path_httpd)',
+ ],
+ },
+ 'dependencies': [
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:include',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:include',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/third_party/apache/apr/apr.gyp:include',
+ '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:include',
+ ],
+ }
+ ],
+ }],
+ ],
+}
--- /dev/null
+/*
+ mod_diagnostics
+
+ Copyright (C) 2003, Nick Kew <nick@webthing.com>
+
+ This is free software. You may use and redistribute it under
+ the terms of the Apache License at
+ http://www.apache.org/LICENSE.txt
+*/
+
+/*
+ mod_diagnostics: print diagnostic and debug information on data
+ (and metadata) passing through an Apache Filter chain.
+
+ Insert a mod_diagnostics filter anywhere you want to watch traffic.
+ See below for registered input and output filter names.
+
+ Two filters are defined for each level, so that you can insert
+ mod_diagnostics before and after any module you are investigating
+ or debugging.
+*/
+
+#include <httpd.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_log.h>
+
+module AP_MODULE_DECLARE_DATA diagnostic_filter_module ;
+
+static void diagnostic_log(ap_filter_t* f, apr_bucket* b) {
+ const char* t ;
+
+ if ( APR_BUCKET_IS_METADATA(b) )
+ t = "(metadata)" ;
+ else
+ t = "(data)" ;
+
+ if ( b->type == &apr_bucket_type_flush )
+ t = "FLUSH" ;
+ else if ( b->type == &apr_bucket_type_eos )
+ t = "EOS" ;
+ else if ( b->type == &apr_bucket_type_file )
+ t = "FILE" ;
+ else if ( b->type == &apr_bucket_type_pipe )
+ t = "PIPE" ;
+ else if ( b->type == &apr_bucket_type_socket )
+ t = "SOCKET" ;
+ else if ( b->type == &apr_bucket_type_heap )
+ t = "HEAP" ;
+ else if ( b->type == &apr_bucket_type_transient )
+ t = "TRANSIENT" ;
+ else if ( b->type == &apr_bucket_type_immortal )
+ t = "IMMORTAL" ;
+ else if ( b->type == &apr_bucket_type_mmap )
+ t = "MMAP" ;
+ else if ( b->type == &apr_bucket_type_pool )
+ t = "POOL" ;
+
+/* use the connection pool, so it works with all filter types
+ (Request may not be valid in a connection or network filter)
+
+ This doesn't work with APLOG_DEBUG (looks like a bug in log.c
+ around line 409 in 2.0.44), so we use APLOG_NOTICE. This is
+ worth updating if httpd gets fixed.
+*/
+ ap_log_perror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, f->c->pool,
+ " %s %s: %d bytes", f->frec->name, t, b->length) ;
+}
+static int diagnostic_ofilter (ap_filter_t* f, apr_bucket_brigade* bb) {
+ apr_bucket* b ;
+
+ ap_log_perror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, f->c->pool, f->frec->name) ;
+
+ for ( b = APR_BRIGADE_FIRST(bb) ;
+ b != APR_BRIGADE_SENTINEL(bb) ;
+ b = APR_BUCKET_NEXT(b) )
+ diagnostic_log(f, b) ;
+
+ return ap_pass_brigade(f->next, bb) ;
+}
+static const char* getmode(ap_input_mode_t mode) {
+ switch ( mode ) {
+ case AP_MODE_READBYTES: return "READBYTES" ;
+ case AP_MODE_GETLINE: return "GETLINE" ;
+ case AP_MODE_EATCRLF: return "EATCRLF" ;
+ case AP_MODE_SPECULATIVE: return "SPECULATIVE" ;
+ case AP_MODE_EXHAUSTIVE: return "EXHAUSTIVE" ;
+ case AP_MODE_INIT: return "INIT" ;
+ }
+ return "(unknown)" ;
+}
+#define gettype(block) ((block) == APR_BLOCK_READ) ? "blocking" : "non-blocking"
+static int diagnostic_ifilter (ap_filter_t* f, apr_bucket_brigade* bb,
+ ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) {
+
+ apr_bucket* b ;
+ apr_status_t ret ;
+
+ ap_log_perror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, f->c->pool,
+ "%s: mode %s; %s; %d bytes", f->frec->name,
+ getmode(mode), gettype(block), readbytes) ;
+
+ if ( ret = ap_get_brigade(f->next, bb, mode, block, readbytes) ,
+ ret == APR_SUCCESS )
+ for ( b = APR_BRIGADE_FIRST(bb) ;
+ b != APR_BRIGADE_SENTINEL(bb) ;
+ b = APR_BUCKET_NEXT(b) )
+ diagnostic_log(f, b) ;
+ else
+ ap_log_perror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, f->c->pool,
+ "%s: ap_get_brigade returned %d", f->frec->name, ret) ;
+
+ return ret ;
+}
+
+
+#define ofilter_init NULL
+#define ifilter_init NULL
+
+/**
+ * Invoked once per connection. See http_connection.h for details.
+ */
+int diagnostic_pre_connection_hook(conn_rec *c, void *csd) {
+ ap_log_cerror(APLOG_MARK,
+ APLOG_NOTICE,
+ APR_SUCCESS,
+ c,
+ "%ld Registering diagnostic filters", c->id);
+
+ ap_add_input_filter("i-connection-1", NULL, NULL, c);
+ ap_add_input_filter("i-connection-2", NULL, NULL, c);
+ ap_add_output_filter("o-connection-1", NULL, NULL, c);
+ //ap_add_input_filter("i-transcode-1", NULL, NULL, c);
+ //ap_add_input_filter("i-protocol-1", NULL, NULL, c);
+ // ap_add_output_filter(g_spdy_output_filter, builder, NULL, c);
+
+ return APR_SUCCESS;
+}
+
+static void diagnostic_hooks(apr_pool_t* p) {
+ ap_hook_pre_connection(
+ diagnostic_pre_connection_hook,
+ NULL,
+ NULL,
+ APR_HOOK_MIDDLE);
+/* by registering twice under each phase, we can insert filters
+ BEFORE and AFTER one we are debugging, and distinguish between them
+
+ I don't think this makes much sense at the network level, but
+ we'll do it anyway: nothing to lose!
+*/
+ ap_register_output_filter("o-resource-1", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_RESOURCE) ;
+ ap_register_output_filter("o-resource-2", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_RESOURCE) ;
+ ap_register_output_filter("o-content-1", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_CONTENT_SET) ;
+ ap_register_output_filter("o-content-2", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_CONTENT_SET) ;
+ ap_register_output_filter("o-protocol-1", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_PROTOCOL) ;
+ ap_register_output_filter("o-protocol-2", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_PROTOCOL) ;
+ ap_register_output_filter("o-transcode-1", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_TRANSCODE) ;
+ ap_register_output_filter("o-transcode-2", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_TRANSCODE) ;
+ ap_register_output_filter("o-connection-1", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_CONNECTION) ;
+ ap_register_output_filter("o-connection-2", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_CONNECTION) ;
+ ap_register_output_filter("o-network-1", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_NETWORK) ;
+ ap_register_output_filter("o-network-2", diagnostic_ofilter,
+ ofilter_init, AP_FTYPE_NETWORK) ;
+
+ ap_register_input_filter("i-resource-1", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_RESOURCE) ;
+ ap_register_input_filter("i-resource-2", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_RESOURCE) ;
+ ap_register_input_filter("i-content-1", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_CONTENT_SET) ;
+ ap_register_input_filter("i-content-2", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_CONTENT_SET) ;
+ ap_register_input_filter("i-protocol-1", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_PROTOCOL) ;
+ ap_register_input_filter("i-protocol-2", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_PROTOCOL) ;
+ ap_register_input_filter("i-transcode-1", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_TRANSCODE) ;
+ ap_register_input_filter("i-transcode-2", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_TRANSCODE) ;
+ ap_register_input_filter("i-connection-1", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_CONNECTION) ;
+ ap_register_input_filter("i-connection-2", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_CONNECTION + 8) ;
+ ap_register_input_filter("i-network-1", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_NETWORK) ;
+ ap_register_input_filter("i-network-2", diagnostic_ifilter,
+ ifilter_init, AP_FTYPE_NETWORK) ;
+}
+
+// Export our module so Apache is able to load us.
+// See http://gcc.gnu.org/wiki/Visibility for more information.
+#if defined(__linux)
+#pragma GCC visibility push(default)
+#endif
+
+module AP_MODULE_DECLARE_DATA diagnostic_filter_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ diagnostic_hooks
+} ;
+
+#if defined(__linux)
+#pragma GCC visibility pop
+#endif
--- /dev/null
+# Copyright 2010 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'mod_diagnostics',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)',
+ ],
+ 'sources': [
+ 'mod_diagnostics.c',
+ ],
+ 'conditions': [['OS == "mac"', {
+ 'xcode_settings': {
+ # We must null out these two variables when building this target,
+ # because it is a loadable_module (-bundle).
+ 'DYLIB_COMPATIBILITY_VERSION':'',
+ 'DYLIB_CURRENT_VERSION':'',
+ }
+ }]],
+ },
+ ],
+}