From: Jim Jagielski Date: Mon, 28 Apr 2014 10:55:17 +0000 (+0000) Subject: Fold in mod_spdy source X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=98bf9a9d84f571ebb5b0300af1449374981e8550;p=thirdparty%2Fapache%2Fhttpd.git Fold in mod_spdy source NOTE: THIS IS THE INITIAL LOAD OF THE SRC AS-IS AND AS DONATED git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1590597 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/spdy/apache/apache_spdy_session_io.cc b/modules/spdy/apache/apache_spdy_session_io.cc new file mode 100644 index 00000000000..7845f8f4d51 --- /dev/null +++ b/modules/spdy/apache/apache_spdy_session_io.cc @@ -0,0 +1,187 @@ +// 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(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 diff --git a/modules/spdy/apache/apache_spdy_session_io.h b/modules/spdy/apache/apache_spdy_session_io.h new file mode 100644 index 00000000000..f47b94cede4 --- /dev/null +++ b/modules/spdy/apache/apache_spdy_session_io.h @@ -0,0 +1,51 @@ +// 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_ diff --git a/modules/spdy/apache/apache_spdy_stream_task_factory.cc b/modules/spdy/apache/apache_spdy_stream_task_factory.cc new file mode 100644 index 00000000000..583b387f097 --- /dev/null +++ b/modules/spdy/apache/apache_spdy_stream_task_factory.cc @@ -0,0 +1,163 @@ +// 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(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(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 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 diff --git a/modules/spdy/apache/apache_spdy_stream_task_factory.h b/modules/spdy/apache/apache_spdy_stream_task_factory.h new file mode 100644 index 00000000000..f0522c38adf --- /dev/null +++ b/modules/spdy/apache/apache_spdy_stream_task_factory.h @@ -0,0 +1,50 @@ +// 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_ diff --git a/modules/spdy/apache/config_commands.cc b/modules/spdy/apache/config_commands.cc new file mode 100644 index 00000000000..79f91c8af3c --- /dev/null +++ b/modules/spdy/apache/config_commands.cc @@ -0,0 +1,171 @@ +// 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(base), + *static_cast(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 +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 +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 +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 +// directive). +template +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( \ + static_cast(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 >, + "Miniumum number of worker threads to spawn per child process"), + SPDY_CONFIG_COMMAND( + "SpdyMaxThreadsPerProcess", + GlobalOnly >, + "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 >, + "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 diff --git a/modules/spdy/apache/config_commands.h b/modules/spdy/apache/config_commands.h new file mode 100644 index 00000000000..1d6504c1411 --- /dev/null +++ b/modules/spdy/apache/config_commands.h @@ -0,0 +1,37 @@ +// 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_ diff --git a/modules/spdy/apache/config_util.cc b/modules/spdy/apache/config_util.cc new file mode 100644 index 00000000000..f2b7290861b --- /dev/null +++ b/modules/spdy/apache/config_util.cc @@ -0,0 +1,139 @@ +// 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 master_context; + scoped_ptr 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(ptr); +} + +ConnectionContext* GetConnContextInternal(conn_rec* connection) { + return static_cast( + 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 diff --git a/modules/spdy/apache/config_util.h b/modules/spdy/apache/config_util.h new file mode 100644 index 00000000000..b3fafd8f276 --- /dev/null +++ b/modules/spdy/apache/config_util.h @@ -0,0 +1,74 @@ +// 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_ diff --git a/modules/spdy/apache/filters/http_to_spdy_filter.cc b/modules/spdy/apache/filters/http_to_spdy_filter.cc new file mode 100644 index 00000000000..184a5dac482 --- /dev/null +++ b/modules/spdy/apache/filters/http_to_spdy_filter.cc @@ -0,0 +1,223 @@ +// 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 . 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(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(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 diff --git a/modules/spdy/apache/filters/http_to_spdy_filter.h b/modules/spdy/apache/filters/http_to_spdy_filter.h new file mode 100644 index 00000000000..a9af95f201a --- /dev/null +++ b/modules/spdy/apache/filters/http_to_spdy_filter.h @@ -0,0 +1,81 @@ +// 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 + +#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_ diff --git a/modules/spdy/apache/filters/http_to_spdy_filter_test.cc b/modules/spdy/apache/filters/http_to_spdy_filter_test.cc new file mode 100644 index 00000000000..79406595719 --- /dev/null +++ b/modules/spdy/apache/filters/http_to_spdy_filter_test.cc @@ -0,0 +1,527 @@ +// 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 + +#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 { + public: + HttpToSpdyFilterTest() + : spdy_version_(GetParam()), + shared_window_(net::kSpdyStreamInitialWindowSize, + net::kSpdyStreamInitialWindowSize), + connection_(static_cast( + apr_pcalloc(local_.pool(), sizeof(conn_rec)))), + ap_filter_(static_cast( + 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 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 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 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 diff --git a/modules/spdy/apache/filters/server_push_filter.cc b/modules/spdy/apache/filters/server_push_filter.cc new file mode 100644 index 00000000000..f9aea1ec481 --- /dev/null +++ b/modules/spdy/apache/filters/server_push_filter.cc @@ -0,0 +1,263 @@ +// 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 + +#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(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(server_push_filter)-> + ParseXAssociatedContentHeader(value); + return 1; // return zero to stop, or non-zero to continue iterating +} + +} // namespace mod_spdy diff --git a/modules/spdy/apache/filters/server_push_filter.h b/modules/spdy/apache/filters/server_push_filter.h new file mode 100644 index 00000000000..8bae87298d6 --- /dev/null +++ b/modules/spdy/apache/filters/server_push_filter.h @@ -0,0 +1,73 @@ +// 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 + +#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_ diff --git a/modules/spdy/apache/filters/server_push_filter_test.cc b/modules/spdy/apache/filters/server_push_filter_test.cc new file mode 100644 index 00000000000..69b0e0662a3 --- /dev/null +++ b/modules/spdy/apache/filters/server_push_filter_test.cc @@ -0,0 +1,489 @@ +// 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 + +#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 { + public: + ServerPushFilterTest() + : spdy_version_(GetParam()), + shared_window_(net::kSpdyStreamInitialWindowSize, + net::kSpdyStreamInitialWindowSize), + connection_(static_cast( + apr_pcalloc(local_.pool(), sizeof(conn_rec)))), + request_(static_cast( + apr_pcalloc(local_.pool(), sizeof(request_rec)))), + ap_filter_(static_cast( + 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("HTTP/1.1"); + request_->unparsed_uri = const_cast(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 diff --git a/modules/spdy/apache/filters/spdy_to_http_filter.cc b/modules/spdy/apache/filters/spdy_to_http_filter.cc new file mode 100644 index 00000000000..ea4e834ef48 --- /dev/null +++ b/modules/spdy/apache/filters/spdy_to_http_filter.cc @@ -0,0 +1,373 @@ +// 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 +#include + +#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(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 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 diff --git a/modules/spdy/apache/filters/spdy_to_http_filter.h b/modules/spdy/apache/filters/spdy_to_http_filter.h new file mode 100644 index 00000000000..779c18a55df --- /dev/null +++ b/modules/spdy/apache/filters/spdy_to_http_filter.h @@ -0,0 +1,108 @@ +// 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 + +#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_ diff --git a/modules/spdy/apache/filters/spdy_to_http_filter_test.cc b/modules/spdy/apache/filters/spdy_to_http_filter_test.cc new file mode 100644 index 00000000000..df047539f2d --- /dev/null +++ b/modules/spdy/apache/filters/spdy_to_http_filter_test.cc @@ -0,0 +1,663 @@ +// 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 + +#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 { + 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( + apr_pcalloc(local_.pool(), sizeof(conn_rec))); + connection_->pool = local_.pool(); + connection_->bucket_alloc = bucket_alloc_; + ap_filter_ = static_cast( + 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 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 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 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 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 diff --git a/modules/spdy/apache/id_pool.cc b/modules/spdy/apache/id_pool.cc new file mode 100644 index 00000000000..0a627f27082 --- /dev/null +++ b/modules/spdy/apache/id_pool.cc @@ -0,0 +1,82 @@ +/* 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 +#include + +#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 diff --git a/modules/spdy/apache/id_pool.h b/modules/spdy/apache/id_pool.h new file mode 100644 index 00000000000..b3cfbf549b5 --- /dev/null +++ b/modules/spdy/apache/id_pool.h @@ -0,0 +1,68 @@ +/* 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 +#include + +#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 free_list_; // IDs known to be free + std::set 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_ */ diff --git a/modules/spdy/apache/id_pool_test.cc b/modules/spdy/apache/id_pool_test.cc new file mode 100644 index 00000000000..7dc71a8f8be --- /dev/null +++ b/modules/spdy/apache/id_pool_test.cc @@ -0,0 +1,97 @@ +// 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 + +#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 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::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 diff --git a/modules/spdy/apache/log_message_handler.cc b/modules/spdy/apache/log_message_handler.cc new file mode 100644 index 00000000000..a513775f449 --- /dev/null +++ b/modules/spdy/apache/log_message_handler.cc @@ -0,0 +1,264 @@ +// 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 +#include + +#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* gThreadLocalLogHandler = NULL; + +const int kMaxInt = std::numeric_limits::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(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(); + 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 diff --git a/modules/spdy/apache/log_message_handler.h b/modules/spdy/apache/log_message_handler.h new file mode 100644 index 00000000000..4cd734c07cf --- /dev/null +++ b/modules/spdy/apache/log_message_handler.h @@ -0,0 +1,84 @@ +// 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 + +#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_ diff --git a/modules/spdy/apache/master_connection_context.cc b/modules/spdy/apache/master_connection_context.cc new file mode 100644 index 00000000000..62fdb1c474d --- /dev/null +++ b/modules/spdy/apache/master_connection_context.cc @@ -0,0 +1,66 @@ +// 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 diff --git a/modules/spdy/apache/master_connection_context.h b/modules/spdy/apache/master_connection_context.h new file mode 100644 index 00000000000..ec8c5e09011 --- /dev/null +++ b/modules/spdy/apache/master_connection_context.h @@ -0,0 +1,89 @@ +// 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 + +#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_ diff --git a/modules/spdy/apache/pool_util.cc b/modules/spdy/apache/pool_util.cc new file mode 100644 index 00000000000..2c65c4e3a7e --- /dev/null +++ b/modules/spdy/apache/pool_util.cc @@ -0,0 +1,31 @@ +// 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 + +#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 diff --git a/modules/spdy/apache/pool_util.h b/modules/spdy/apache/pool_util.h new file mode 100644 index 00000000000..548bfd112cc --- /dev/null +++ b/modules/spdy/apache/pool_util.h @@ -0,0 +1,91 @@ +// 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 + +#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 +apr_status_t DeletionFunction(void* object) { + delete static_cast(object); + return APR_SUCCESS; +} + +// Register a C++ object to be deleted with a pool. +template +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, // 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 +void PoolUnregisterDelete(apr_pool_t* pool, T* object) { + apr_pool_cleanup_kill(pool, object, DeletionFunction); +} + +// 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_ diff --git a/modules/spdy/apache/pool_util_test.cc b/modules/spdy/apache/pool_util_test.cc new file mode 100644 index 00000000000..87dbfe13a33 --- /dev/null +++ b/modules/spdy/apache/pool_util_test.cc @@ -0,0 +1,59 @@ +// 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 diff --git a/modules/spdy/apache/slave_connection.cc b/modules/spdy/apache/slave_connection.cc new file mode 100644 index 00000000000..884c6d133fe --- /dev/null +++ b/modules/spdy/apache/slave_connection.cc @@ -0,0 +1,190 @@ +// 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(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 diff --git a/modules/spdy/apache/slave_connection.h b/modules/spdy/apache/slave_connection.h new file mode 100644 index 00000000000..26507800121 --- /dev/null +++ b/modules/spdy/apache/slave_connection.h @@ -0,0 +1,112 @@ +/* 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_ */ diff --git a/modules/spdy/apache/slave_connection_api.cc b/modules/spdy/apache/slave_connection_api.cc new file mode 100644 index 00000000000..25a64c080bf --- /dev/null +++ b/modules/spdy/apache/slave_connection_api.cc @@ -0,0 +1,79 @@ +/* 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 impl; +}; + +struct spdy_slave_connection { + explicit spdy_slave_connection(SlaveConnection* impl) + : impl(impl) {} + scoped_ptr 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); +} diff --git a/modules/spdy/apache/slave_connection_api.h b/modules/spdy/apache/slave_connection_api.h new file mode 100644 index 00000000000..465e2a1a06b --- /dev/null +++ b/modules/spdy/apache/slave_connection_api.h @@ -0,0 +1,79 @@ +/* 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_ */ diff --git a/modules/spdy/apache/slave_connection_context.cc b/modules/spdy/apache/slave_connection_context.cc new file mode 100644 index 00000000000..0c3548608c6 --- /dev/null +++ b/modules/spdy/apache/slave_connection_context.cc @@ -0,0 +1,46 @@ +// 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 diff --git a/modules/spdy/apache/slave_connection_context.h b/modules/spdy/apache/slave_connection_context.h new file mode 100644 index 00000000000..1b9032d2ac4 --- /dev/null +++ b/modules/spdy/apache/slave_connection_context.h @@ -0,0 +1,99 @@ +// 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 + +#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_ diff --git a/modules/spdy/apache/sockaddr_util.cc b/modules/spdy/apache/sockaddr_util.cc new file mode 100644 index 00000000000..3e373c69a16 --- /dev/null +++ b/modules/spdy/apache/sockaddr_util.cc @@ -0,0 +1,55 @@ +// 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 // for ptrdiff_t +#include + +#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_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(in->ipaddr_ptr) - reinterpret_cast(in); + out->ipaddr_ptr = reinterpret_cast(out) + ipaddr_ptr_offset; + } + + if (in->next != NULL) { + out->next = DeepCopySockAddr(in->next, pool); + } + + return out; +} + +} // namespace mod_spdy diff --git a/modules/spdy/apache/sockaddr_util.h b/modules/spdy/apache/sockaddr_util.h new file mode 100644 index 00000000000..3a033509216 --- /dev/null +++ b/modules/spdy/apache/sockaddr_util.h @@ -0,0 +1,29 @@ +// 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_ diff --git a/modules/spdy/apache/sockaddr_util_test.cc b/modules/spdy/apache/sockaddr_util_test.cc new file mode 100644 index 00000000000..aa23c189b34 --- /dev/null +++ b/modules/spdy/apache/sockaddr_util_test.cc @@ -0,0 +1,104 @@ +// 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 diff --git a/modules/spdy/apache/ssl_util.cc b/modules/spdy/apache/ssl_util.cc new file mode 100644 index 00000000000..19198181751 --- /dev/null +++ b/modules/spdy/apache/ssl_util.cc @@ -0,0 +1,79 @@ +// 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 diff --git a/modules/spdy/apache/ssl_util.h b/modules/spdy/apache/ssl_util.h new file mode 100644 index 00000000000..2b3cc9b3ebd --- /dev/null +++ b/modules/spdy/apache/ssl_util.h @@ -0,0 +1,43 @@ +// 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_ diff --git a/modules/spdy/apache/testing/dummy_util_filter.cc b/modules/spdy/apache/testing/dummy_util_filter.cc new file mode 100644 index 00000000000..5835e058976 --- /dev/null +++ b/modules/spdy/apache/testing/dummy_util_filter.cc @@ -0,0 +1,32 @@ +// 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" diff --git a/modules/spdy/apache/testing/spdy_apache_test_main.cc b/modules/spdy/apache/testing/spdy_apache_test_main.cc new file mode 100644 index 00000000000..6f5bbeab64a --- /dev/null +++ b/modules/spdy/apache/testing/spdy_apache_test_main.cc @@ -0,0 +1,46 @@ +// 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 + +#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(); +} diff --git a/modules/spdy/common/VERSION b/modules/spdy/common/VERSION new file mode 100644 index 00000000000..7925983ebf0 --- /dev/null +++ b/modules/spdy/common/VERSION @@ -0,0 +1,4 @@ +MAJOR=0 +MINOR=1 +BUILD=0 +PATCH=0 diff --git a/modules/spdy/common/executor.cc b/modules/spdy/common/executor.cc new file mode 100644 index 00000000000..771a01f97d4 --- /dev/null +++ b/modules/spdy/common/executor.cc @@ -0,0 +1,23 @@ +// 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 diff --git a/modules/spdy/common/executor.h b/modules/spdy/common/executor.h new file mode 100644 index 00000000000..0da3346d8b7 --- /dev/null +++ b/modules/spdy/common/executor.h @@ -0,0 +1,53 @@ +// 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_ diff --git a/modules/spdy/common/http_request_visitor_interface.cc b/modules/spdy/common/http_request_visitor_interface.cc new file mode 100644 index 00000000000..c7bd459d162 --- /dev/null +++ b/modules/spdy/common/http_request_visitor_interface.cc @@ -0,0 +1,22 @@ +// 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 diff --git a/modules/spdy/common/http_request_visitor_interface.h b/modules/spdy/common/http_request_visitor_interface.h new file mode 100644 index 00000000000..8f69ea6f14e --- /dev/null +++ b/modules/spdy/common/http_request_visitor_interface.h @@ -0,0 +1,86 @@ +// 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_ diff --git a/modules/spdy/common/http_response_parser.cc b/modules/spdy/common/http_response_parser.cc new file mode 100644 index 00000000000..45ec9536c27 --- /dev/null +++ b/modules/spdy/common/http_response_parser.cc @@ -0,0 +1,367 @@ +// 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 + +#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: + // + // 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(int_value); + return true; +} + +} // namespace mod_spdy diff --git a/modules/spdy/common/http_response_parser.h b/modules/spdy/common/http_response_parser.h new file mode 100644 index 00000000000..193f7b4cd9b --- /dev/null +++ b/modules/spdy/common/http_response_parser.h @@ -0,0 +1,89 @@ +// 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 + +#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_ diff --git a/modules/spdy/common/http_response_parser_test.cc b/modules/spdy/common/http_response_parser_test.cc new file mode 100644 index 00000000000..35acaa04ab4 --- /dev/null +++ b/modules/spdy/common/http_response_parser_test.cc @@ -0,0 +1,273 @@ +// 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 diff --git a/modules/spdy/common/http_response_visitor_interface.cc b/modules/spdy/common/http_response_visitor_interface.cc new file mode 100644 index 00000000000..2fd0511e1a0 --- /dev/null +++ b/modules/spdy/common/http_response_visitor_interface.cc @@ -0,0 +1,22 @@ +// 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 diff --git a/modules/spdy/common/http_response_visitor_interface.h b/modules/spdy/common/http_response_visitor_interface.h new file mode 100644 index 00000000000..bd5754e6549 --- /dev/null +++ b/modules/spdy/common/http_response_visitor_interface.h @@ -0,0 +1,58 @@ +// 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_ diff --git a/modules/spdy/common/http_string_builder.cc b/modules/spdy/common/http_string_builder.cc new file mode 100644 index 00000000000..a3f2c9cb4c1 --- /dev/null +++ b/modules/spdy/common/http_string_builder.cc @@ -0,0 +1,122 @@ +// 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 + +#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(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 diff --git a/modules/spdy/common/http_string_builder.h b/modules/spdy/common/http_string_builder.h new file mode 100644 index 00000000000..5a28f63b754 --- /dev/null +++ b/modules/spdy/common/http_string_builder.h @@ -0,0 +1,69 @@ +// 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 + +#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_ diff --git a/modules/spdy/common/http_to_spdy_converter.cc b/modules/spdy/common/http_to_spdy_converter.cc new file mode 100644 index 00000000000..fa46c65d4f4 --- /dev/null +++ b/modules/spdy/common/http_to_spdy_converter.cc @@ -0,0 +1,192 @@ +// 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 + +#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 diff --git a/modules/spdy/common/http_to_spdy_converter.h b/modules/spdy/common/http_to_spdy_converter.h new file mode 100644 index 00000000000..592bea642d0 --- /dev/null +++ b/modules/spdy/common/http_to_spdy_converter.h @@ -0,0 +1,78 @@ +// 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 + +#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 impl_; + HttpResponseParser parser_; + + DISALLOW_COPY_AND_ASSIGN(HttpToSpdyConverter); +}; + +} // namespace mod_spdy + +#endif // MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_ diff --git a/modules/spdy/common/http_to_spdy_converter_test.cc b/modules/spdy/common/http_to_spdy_converter_test.cc new file mode 100644 index 00000000000..290b243be4e --- /dev/null +++ b/modules/spdy/common/http_to_spdy_converter_test.cc @@ -0,0 +1,259 @@ +// 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 { + 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 diff --git a/modules/spdy/common/protocol_util.cc b/modules/spdy/common/protocol_util.cc new file mode 100644 index 00000000000..04212373998 --- /dev/null +++ b/modules/spdy/common/protocol_util.cc @@ -0,0 +1,139 @@ +// 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(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 ""; + } +} + +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 ""; + } +} + +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 diff --git a/modules/spdy/common/protocol_util.h b/modules/spdy/common/protocol_util.h new file mode 100644 index 00000000000..9877a6119fb --- /dev/null +++ b/modules/spdy/common/protocol_util.h @@ -0,0 +1,105 @@ +// 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_ diff --git a/modules/spdy/common/protocol_util_test.cc b/modules/spdy/common/protocol_util_test.cc new file mode 100644 index 00000000000..8dc98ecf66b --- /dev/null +++ b/modules/spdy/common/protocol_util_test.cc @@ -0,0 +1,95 @@ +// 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 diff --git a/modules/spdy/common/server_push_discovery_learner.cc b/modules/spdy/common/server_push_discovery_learner.cc new file mode 100644 index 00000000000..50939e79c5c --- /dev/null +++ b/modules/spdy/common/server_push_discovery_learner.cc @@ -0,0 +1,112 @@ +// 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 +#include + +#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::GetPushes(const std::string& master_url) { + base::AutoLock lock(lock_); + UrlData& url_data = url_data_[master_url]; + std::vector pushes; + + uint64_t threshold = url_data.first_hit_count / 2; + + std::vector significant_adjacents; + + for (std::map::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& 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 diff --git a/modules/spdy/common/server_push_discovery_learner.h b/modules/spdy/common/server_push_discovery_learner.h new file mode 100644 index 00000000000..a25fe8bc77d --- /dev/null +++ b/modules/spdy/common/server_push_discovery_learner.h @@ -0,0 +1,88 @@ +// 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 +#include +#include + +#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 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 adjacents; + }; + + static bool CompareAdjacentDataByAverageTimeFromInit(const AdjacentData& a, + const AdjacentData& b); + + std::map url_data_; + base::Lock lock_; +}; + +} // namespace mod_spdy + +#endif // MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_ diff --git a/modules/spdy/common/server_push_discovery_learner_test.cc b/modules/spdy/common/server_push_discovery_learner_test.cc new file mode 100644 index 00000000000..c85d5eabaaa --- /dev/null +++ b/modules/spdy/common/server_push_discovery_learner_test.cc @@ -0,0 +1,101 @@ +// 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 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 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 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 diff --git a/modules/spdy/common/server_push_discovery_session.cc b/modules/spdy/common/server_push_discovery_session.cc new file mode 100644 index 00000000000..480f1037d30 --- /dev/null +++ b/modules/spdy/common/server_push_discovery_session.cc @@ -0,0 +1,80 @@ +// 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::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::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 diff --git a/modules/spdy/common/server_push_discovery_session.h b/modules/spdy/common/server_push_discovery_session.h new file mode 100644 index 00000000000..7bfd2449fac --- /dev/null +++ b/modules/spdy/common/server_push_discovery_session.h @@ -0,0 +1,97 @@ +// 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 +#include + +#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 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_ diff --git a/modules/spdy/common/server_push_discovery_session_test.cc b/modules/spdy/common/server_push_discovery_session_test.cc new file mode 100644 index 00000000000..e49f1f42373 --- /dev/null +++ b/modules/spdy/common/server_push_discovery_session_test.cc @@ -0,0 +1,56 @@ +// 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 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 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 diff --git a/modules/spdy/common/shared_flow_control_window.cc b/modules/spdy/common/shared_flow_control_window.cc new file mode 100644 index 00000000000..cbf333c5f64 --- /dev/null +++ b/modules/spdy/common/shared_flow_control_window.cc @@ -0,0 +1,161 @@ +// 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(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(input_bytes_consumed_) + static_cast(length); + CHECK_LE(new_input_bytes_consumed, + static_cast(init_input_window_size_)); + CHECK_LE(new_input_bytes_consumed + static_cast(input_window_size_), + static_cast(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(output_window_size_) + static_cast(delta); + if (new_size > static_cast(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 diff --git a/modules/spdy/common/shared_flow_control_window.h b/modules/spdy/common/shared_flow_control_window.h new file mode 100644 index 00000000000..6b217e886c1 --- /dev/null +++ b/modules/spdy/common/shared_flow_control_window.h @@ -0,0 +1,107 @@ +// 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_ diff --git a/modules/spdy/common/shared_flow_control_window_test.cc b/modules/spdy/common/shared_flow_control_window_test.cc new file mode 100644 index 00000000000..09212e222b9 --- /dev/null +++ b/modules/spdy/common/shared_flow_control_window_test.cc @@ -0,0 +1,216 @@ +// 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 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 diff --git a/modules/spdy/common/spdy_frame_priority_queue.cc b/modules/spdy/common/spdy_frame_priority_queue.cc new file mode 100644 index 00000000000..d7f2c6152b0 --- /dev/null +++ b/modules/spdy/common/spdy_frame_priority_queue.cc @@ -0,0 +1,120 @@ +// 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 +#include + +#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 diff --git a/modules/spdy/common/spdy_frame_priority_queue.h b/modules/spdy/common/spdy_frame_priority_queue.h new file mode 100644 index 00000000000..c6266d73aca --- /dev/null +++ b/modules/spdy/common/spdy_frame_priority_queue.h @@ -0,0 +1,95 @@ +// 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 +#include + +#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 FrameList; + typedef std::map QueueMap; + QueueMap queue_map_; + + DISALLOW_COPY_AND_ASSIGN(SpdyFramePriorityQueue); +}; + +} // namespace mod_spdy + +#endif // MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_ diff --git a/modules/spdy/common/spdy_frame_priority_queue_test.cc b/modules/spdy/common/spdy_frame_priority_queue_test.cc new file mode 100644 index 00000000000..30d7f7c5855 --- /dev/null +++ b/modules/spdy/common/spdy_frame_priority_queue_test.cc @@ -0,0 +1,139 @@ +// 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 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 diff --git a/modules/spdy/common/spdy_frame_queue.cc b/modules/spdy/common/spdy_frame_queue.cc new file mode 100644 index 00000000000..8b530e2d57c --- /dev/null +++ b/modules/spdy/common/spdy_frame_queue.cc @@ -0,0 +1,84 @@ +// 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 + +#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 diff --git a/modules/spdy/common/spdy_frame_queue.h b/modules/spdy/common/spdy_frame_queue.h new file mode 100644 index 00000000000..c8a24d7e59e --- /dev/null +++ b/modules/spdy/common/spdy_frame_queue.h @@ -0,0 +1,71 @@ +// 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 + +#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 queue_; + bool is_aborted_; + + DISALLOW_COPY_AND_ASSIGN(SpdyFrameQueue); +}; + +} // namespace mod_spdy + +#endif // MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_ diff --git a/modules/spdy/common/spdy_frame_queue_test.cc b/modules/spdy/common/spdy_frame_queue_test.cc new file mode 100644 index 00000000000..b767f82a78b --- /dev/null +++ b/modules/spdy/common/spdy_frame_queue_test.cc @@ -0,0 +1,112 @@ +// 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 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 diff --git a/modules/spdy/common/spdy_server_config.cc b/modules/spdy/common/spdy_server_config.cc new file mode 100644 index 00000000000..0f6192f0793 --- /dev/null +++ b/modules/spdy/common/spdy_server_config.cc @@ -0,0 +1,65 @@ +// 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 diff --git a/modules/spdy/common/spdy_server_config.h b/modules/spdy/common/spdy_server_config.h new file mode 100644 index 00000000000..7a997131326 --- /dev/null +++ b/modules/spdy/common/spdy_server_config.h @@ -0,0 +1,121 @@ +// 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 + 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& a, const Option& 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 spdy_enabled_; + Option max_streams_per_connection_; + Option min_threads_per_process_; + Option max_threads_per_process_; + Option max_server_push_depth_; + Option send_version_header_; + Option use_spdy_version_without_ssl_; + Option 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_ diff --git a/modules/spdy/common/spdy_server_push_interface.cc b/modules/spdy/common/spdy_server_push_interface.cc new file mode 100644 index 00000000000..73a261cad1a --- /dev/null +++ b/modules/spdy/common/spdy_server_push_interface.cc @@ -0,0 +1,23 @@ +// 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 diff --git a/modules/spdy/common/spdy_server_push_interface.h b/modules/spdy/common/spdy_server_push_interface.h new file mode 100644 index 00000000000..66ea7681aaa --- /dev/null +++ b/modules/spdy/common/spdy_server_push_interface.h @@ -0,0 +1,66 @@ +// 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_ diff --git a/modules/spdy/common/spdy_session.cc b/modules/spdy/common/spdy_session.cc new file mode 100644 index 00000000000..9b40f6eaba8 --- /dev/null +++ b/modules/spdy/common/spdy_session.cc @@ -0,0 +1,914 @@ +// 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(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(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(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 frame(frame_ptr); + scoped_ptr 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 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 diff --git a/modules/spdy/common/spdy_session.h b/modules/spdy/common/spdy_session.h new file mode 100644 index 00000000000..d27d23b09ee --- /dev/null +++ b/modules/spdy/common/spdy_session.h @@ -0,0 +1,264 @@ +// 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 + +#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 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_ diff --git a/modules/spdy/common/spdy_session_io.cc b/modules/spdy/common/spdy_session_io.cc new file mode 100644 index 00000000000..c505c4c10d9 --- /dev/null +++ b/modules/spdy/common/spdy_session_io.cc @@ -0,0 +1,23 @@ +// 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 diff --git a/modules/spdy/common/spdy_session_io.h b/modules/spdy/common/spdy_session_io.h new file mode 100644 index 00000000000..77600b8e958 --- /dev/null +++ b/modules/spdy/common/spdy_session_io.h @@ -0,0 +1,82 @@ +// 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_ diff --git a/modules/spdy/common/spdy_session_test.cc b/modules/spdy/common/spdy_session_test.cc new file mode 100644 index 00000000000..abbbc63f231 --- /dev/null +++ b/modules/spdy/common/spdy_session_test.cc @@ -0,0 +1,1070 @@ +// 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 +#include + +#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 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 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 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 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 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 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 last_frame_; + + DISALLOW_COPY_AND_ASSIGN(ClientVisitor); +}; + +ACTION_P2(ClientDecodeFrame, test, matcher) { + scoped_ptr 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 { + 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 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 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 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 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 frame( + client_framer_.CreateWindowUpdate(stream_id, delta)); + ReceiveFrameFromClient(*frame); + } + + protected: + void ExpectSendFrame(::testing::Matcher 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 session_io_; + StrictMock task_factory_; + std::list 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 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 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 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 executor_; + scoped_ptr 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 diff --git a/modules/spdy/common/spdy_stream.cc b/modules/spdy/common/spdy_stream.cc new file mode 100644 index 00000000000..c550ad0de65 --- /dev/null +++ b/modules/spdy/common/spdy_stream.cc @@ -0,0 +1,456 @@ +// 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(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(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(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(output_window_size_) + static_cast(delta); + if (new_size > static_cast(net::kSpdyMaximumWindowSize) || + new_size < -static_cast(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(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 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 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 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 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 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(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 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(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 diff --git a/modules/spdy/common/spdy_stream.h b/modules/spdy/common/spdy_stream.h new file mode 100644 index 00000000000..67dd2149f20 --- /dev/null +++ b/modules/spdy/common/spdy_stream.h @@ -0,0 +1,187 @@ +// 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_ diff --git a/modules/spdy/common/spdy_stream_task_factory.cc b/modules/spdy/common/spdy_stream_task_factory.cc new file mode 100644 index 00000000000..f27ffd0e7e4 --- /dev/null +++ b/modules/spdy/common/spdy_stream_task_factory.cc @@ -0,0 +1,23 @@ +// 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 diff --git a/modules/spdy/common/spdy_stream_task_factory.h b/modules/spdy/common/spdy_stream_task_factory.h new file mode 100644 index 00000000000..add9235152b --- /dev/null +++ b/modules/spdy/common/spdy_stream_task_factory.h @@ -0,0 +1,49 @@ +// 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_ diff --git a/modules/spdy/common/spdy_stream_test.cc b/modules/spdy/common/spdy_stream_test.cc new file mode 100644 index 00000000000..fe6bee31631 --- /dev/null +++ b/modules/spdy/common/spdy_stream_test.cc @@ -0,0 +1,596 @@ +// 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 + +#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 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 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 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 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 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 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 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 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 diff --git a/modules/spdy/common/spdy_to_http_converter.cc b/modules/spdy/common/spdy_to_http_converter.cc new file mode 100644 index 00000000000..78849154e78 --- /dev/null +++ b/modules/spdy/common/spdy_to_http_converter.cc @@ -0,0 +1,337 @@ +// 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 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 diff --git a/modules/spdy/common/spdy_to_http_converter.h b/modules/spdy/common/spdy_to_http_converter.h new file mode 100644 index 00000000000..0b2d2b8f3eb --- /dev/null +++ b/modules/spdy/common/spdy_to_http_converter.h @@ -0,0 +1,85 @@ +// 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_ diff --git a/modules/spdy/common/spdy_to_http_converter_test.cc b/modules/spdy/common/spdy_to_http_converter_test.cc new file mode 100644 index 00000000000..96a3b724b48 --- /dev/null +++ b/modules/spdy/common/spdy_to_http_converter_test.cc @@ -0,0 +1,340 @@ +// 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 { + 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 diff --git a/modules/spdy/common/testing/async_task_runner.cc b/modules/spdy/common/testing/async_task_runner.cc new file mode 100644 index 00000000000..a3a5df22af7 --- /dev/null +++ b/modules/spdy/common/testing/async_task_runner.cc @@ -0,0 +1,73 @@ +// 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 diff --git a/modules/spdy/common/testing/async_task_runner.h b/modules/spdy/common/testing/async_task_runner.h new file mode 100644 index 00000000000..01b7520b905 --- /dev/null +++ b/modules/spdy/common/testing/async_task_runner.h @@ -0,0 +1,81 @@ +// 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_; + mod_spdy::ThreadPool thread_pool_; + scoped_ptr executor_; + Notification notification_; + + DISALLOW_COPY_AND_ASSIGN(AsyncTaskRunner); +}; + +} // namespace testing + +} // namespace mod_spdy + +#endif // MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_ diff --git a/modules/spdy/common/testing/notification.cc b/modules/spdy/common/testing/notification.cc new file mode 100644 index 00000000000..4e0e7eeb5dd --- /dev/null +++ b/modules/spdy/common/testing/notification.cc @@ -0,0 +1,66 @@ +// 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 diff --git a/modules/spdy/common/testing/notification.h b/modules/spdy/common/testing/notification.h new file mode 100644 index 00000000000..169978ce769 --- /dev/null +++ b/modules/spdy/common/testing/notification.h @@ -0,0 +1,59 @@ +// 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_ diff --git a/modules/spdy/common/testing/spdy_frame_matchers.cc b/modules/spdy/common/testing/spdy_frame_matchers.cc new file mode 100644 index 00000000000..923158b38b7 --- /dev/null +++ b/modules/spdy/common/testing/spdy_frame_matchers.cc @@ -0,0 +1,271 @@ +// 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 +#include + +#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(syn_stream.stream_id()), + static_cast(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(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(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(iter->second.value)); + } + out_->append(")"); + } + virtual void VisitPing(const net::SpdyPingIR& ping) { + base::StringAppendF( + out_, "PING(%u)", static_cast(ping.id())); + } + virtual void VisitGoAway(const net::SpdyGoAwayIR& goaway) { + base::StringAppendF( + out_, "GOAWAY(%u %s)", + static_cast(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(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(window.stream_id()), + static_cast(window.delta())); + } + virtual void VisitCredential(const net::SpdyCredentialIR& credential) { + // TODO(mdsteele): include other fields + base::StringAppendF( + out_, "CREDENTIAL(%d)", static_cast(credential.slot())); + } + virtual void VisitBlocked(const net::SpdyBlockedIR& blocked) { + base::StringAppendF( + out_, "BLOCKED(%u)", static_cast(blocked.stream_id())); + } + virtual void VisitPushPromise(const net::SpdyPushPromiseIR& push_promise) { + base::StringAppendF( + out_, "PUSH_PROMISE(%u, %u)", + static_cast(push_promise.stream_id()), + static_cast(push_promise.promised_stream_id())); + } + virtual void VisitData(const net::SpdyDataIR& data) { + base::StringAppendF( + out_, "DATA(%u%s \"", static_cast(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 { + 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 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 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 IsRstStream( + net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status) { + net::SpdyRstStreamIR frame(stream_id, status); + return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame)); +} + +::testing::Matcher IsSettings( + net::SpdySettingsIds id, int32 value) { + net::SpdySettingsIR frame; + frame.AddSetting(id, false, false, value); + return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame)); +} + +::testing::Matcher IsPing(net::SpdyPingId ping_id) { + net::SpdyPingIR frame(ping_id); + return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame)); +} + +::testing::Matcher 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 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 IsWindowUpdate( + net::SpdyStreamId stream_id, uint32 delta) { + net::SpdyWindowUpdateIR frame(stream_id, delta); + return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame)); +} + +::testing::Matcher 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 diff --git a/modules/spdy/common/testing/spdy_frame_matchers.h b/modules/spdy/common/testing/spdy_frame_matchers.h new file mode 100644 index 00000000000..847dca680c3 --- /dev/null +++ b/modules/spdy/common/testing/spdy_frame_matchers.h @@ -0,0 +1,79 @@ +// 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 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 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 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 IsSettings( + net::SpdySettingsIds id, int32 value); + +// Make a matcher that requires the argument to be a PING frame with the +// given ID. +::testing::Matcher 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 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 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 IsWindowUpdate( + net::SpdyStreamId stream_id, uint32 delta); + +// Make a matcher that requires the argument to be a DATA frame. +::testing::Matcher IsDataFrame( + net::SpdyStreamId stream_id, bool fin, base::StringPiece payload); + +} // namespace testing + +} // namespace mod_spdy + +#endif // MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_ diff --git a/modules/spdy/common/thread_pool.cc b/modules/spdy/common/thread_pool.cc new file mode 100644 index 00000000000..b5224b98275 --- /dev/null +++ b/modules/spdy/common/thread_pool.cc @@ -0,0 +1,490 @@ +// 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 +#include +#include + +#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 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 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::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 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 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 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& threads) { + for (std::set::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 diff --git a/modules/spdy/common/thread_pool.h b/modules/spdy/common/thread_pool.h new file mode 100644 index 00000000000..79d103ba83f --- /dev/null +++ b/modules/spdy/common/thread_pool.h @@ -0,0 +1,145 @@ +// 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 +#include + +#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 TaskQueue; + typedef std::map 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& 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 workers_; + // Worker threads that have shut themselves down (due to being idle), and are + // awaiting cleanup by the master thread. + std::set 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_ diff --git a/modules/spdy/common/thread_pool_test.cc b/modules/spdy/common/thread_pool_test.cc new file mode 100644 index 00000000000..5a2393d1568 --- /dev/null +++ b/modules/spdy/common/thread_pool_test.cc @@ -0,0 +1,339 @@ +// 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 + +#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 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 executor1(thread_pool.NewExecutor()); + scoped_ptr 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* 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* 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 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 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(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 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 diff --git a/modules/spdy/common/version.h.in b/modules/spdy/common/version.h.in new file mode 100644 index 00000000000..bea11206b59 --- /dev/null +++ b/modules/spdy/common/version.h.in @@ -0,0 +1,25 @@ +// 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@" diff --git a/modules/spdy/mod_spdy.cc b/modules/spdy/mod_spdy.cc new file mode 100644 index 00000000000..8bba0e1c448 --- /dev/null +++ b/modules/spdy/mod_spdy.cc @@ -0,0 +1,850 @@ +// 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 // 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(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 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 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_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" diff --git a/modules/spdy/mod_spdy.gyp b/modules/spdy/mod_spdy.gyp new file mode 100644 index 00000000000..169abf68de2 --- /dev/null +++ b/modules/spdy/mod_spdy.gyp @@ -0,0 +1,199 @@ +# 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', + ], + }, + ], +} diff --git a/modules/spdy/mod_spdy.h b/modules/spdy/mod_spdy.h new file mode 100644 index 00000000000..bbc68379d3e --- /dev/null +++ b/modules/spdy/mod_spdy.h @@ -0,0 +1,47 @@ +/* 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_ */ diff --git a/modules/spdy/support/DEPS b/modules/spdy/support/DEPS new file mode 100644 index 00000000000..0a9ddd74e08 --- /dev/null +++ b/modules/spdy/support/DEPS @@ -0,0 +1,142 @@ +# 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"], + }, +] diff --git a/modules/spdy/support/base/base.gyp b/modules/spdy/support/base/base.gyp new file mode 100644 index 00000000000..ec44e1a2707 --- /dev/null +++ b/modules/spdy/support/base/base.gyp @@ -0,0 +1,135 @@ +# 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', + ], + }, + }], + ], + }, + ], +} diff --git a/modules/spdy/support/base/metrics/stats_table.cc b/modules/spdy/support/base/metrics/stats_table.cc new file mode 100644 index 00000000000..26fe3b0af50 --- /dev/null +++ b/modules/spdy/support/base/metrics/stats_table.cc @@ -0,0 +1,31 @@ +// 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 diff --git a/modules/spdy/support/build/all.gyp b/modules/spdy/support/build/all.gyp new file mode 100644 index 00000000000..56dfb4d4be8 --- /dev/null +++ b/modules/spdy/support/build/all.gyp @@ -0,0 +1,28 @@ +# 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:*', + ],} ] +} diff --git a/modules/spdy/support/build/build_config.h b/modules/spdy/support/build/build_config.h new file mode 100644 index 00000000000..1169a31fa80 --- /dev/null +++ b/modules/spdy/support/build/build_config.h @@ -0,0 +1 @@ +#include "third_party/chromium/src/build/build_config.h" diff --git a/modules/spdy/support/build/build_util.gyp b/modules/spdy/support/build/build_util.gyp new file mode 100644 index 00000000000..1ff19722f81 --- /dev/null +++ b/modules/spdy/support/build/build_util.gyp @@ -0,0 +1,84 @@ +# 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: diff --git a/modules/spdy/support/build/common.gypi b/modules/spdy/support/build/common.gypi new file mode 100644 index 00000000000..c11cbea35f0 --- /dev/null +++ b/modules/spdy/support/build/common.gypi @@ -0,0 +1,49 @@ +# 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', + ], + }, +} diff --git a/modules/spdy/support/build/compiler_version.py b/modules/spdy/support/build/compiler_version.py new file mode 100644 index 00000000000..be71db2cf23 --- /dev/null +++ b/modules/spdy/support/build/compiler_version.py @@ -0,0 +1,24 @@ +#!/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')) diff --git a/modules/spdy/support/build/dir_exists.py b/modules/spdy/support/build/dir_exists.py new file mode 100644 index 00000000000..5a9948c1293 --- /dev/null +++ b/modules/spdy/support/build/dir_exists.py @@ -0,0 +1,24 @@ +#!/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')) diff --git a/modules/spdy/support/build/features_override.gypi b/modules/spdy/support/build/features_override.gypi new file mode 100644 index 00000000000..52409ba8beb --- /dev/null +++ b/modules/spdy/support/build/features_override.gypi @@ -0,0 +1,18 @@ +# 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. + +{} diff --git a/modules/spdy/support/build/gyp_chromium b/modules/spdy/support/build/gyp_chromium new file mode 100644 index 00000000000..12054f49a76 --- /dev/null +++ b/modules/spdy/support/build/gyp_chromium @@ -0,0 +1,30 @@ +#!/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')) diff --git a/modules/spdy/support/build/gyp_helper.py b/modules/spdy/support/build/gyp_helper.py new file mode 100755 index 00000000000..ac22b07d3cf --- /dev/null +++ b/modules/spdy/support/build/gyp_helper.py @@ -0,0 +1,24 @@ +#!/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')) diff --git a/modules/spdy/support/build/install.gyp b/modules/spdy/support/build/install.gyp new file mode 100644 index 00000000000..5b677275d1f --- /dev/null +++ b/modules/spdy/support/build/install.gyp @@ -0,0 +1,200 @@ +# 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' : ' + + + + + + + + diff --git a/modules/spdy/support/build/release.gypi b/modules/spdy/support/build/release.gypi new file mode 100644 index 00000000000..c12526b8130 --- /dev/null +++ b/modules/spdy/support/build/release.gypi @@ -0,0 +1,19 @@ +{ + '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 + ], +} + diff --git a/modules/spdy/support/build/version.py b/modules/spdy/support/build/version.py new file mode 100755 index 00000000000..428f7610cbc --- /dev/null +++ b/modules/spdy/support/build/version.py @@ -0,0 +1,25 @@ +#!/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')) diff --git a/modules/spdy/support/build_modssl_with_npn.sh b/modules/spdy/support/build_modssl_with_npn.sh new file mode 100755 index 00000000000..3213c9eb90d --- /dev/null +++ b/modules/spdy/support/build_modssl_with_npn.sh @@ -0,0 +1,240 @@ +#!/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." diff --git a/modules/spdy/support/install/common/BRANDING b/modules/spdy/support/install/common/BRANDING new file mode 100644 index 00000000000..fb563f116b1 --- /dev/null +++ b/modules/spdy/support/install/common/BRANDING @@ -0,0 +1,5 @@ +COMPANY_FULLNAME=Google Inc. +COMPANY_SHORTNAME=Google Inc. +PRODUCT_FULLNAME=mod_spdy +PRODUCT_SHORTNAME=mod_spdy +COPYRIGHT=Copyright (C) 2012. diff --git a/modules/spdy/support/install/common/apt.include b/modules/spdy/support/install/common/apt.include new file mode 100644 index 00000000000..6c2641721de --- /dev/null +++ b/modules/spdy/support/install/common/apt.include @@ -0,0 +1,188 @@ +@@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 </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 +} + diff --git a/modules/spdy/support/install/common/installer.include b/modules/spdy/support/install/common/installer.include new file mode 100644 index 00000000000..80ed37dcd01 --- /dev/null +++ b/modules/spdy/support/install/common/installer.include @@ -0,0 +1,116 @@ +# 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" +} diff --git a/modules/spdy/support/install/common/mod-spdy.info b/modules/spdy/support/install/common/mod-spdy.info new file mode 100644 index 00000000000..9c39eaf85c7 --- /dev/null +++ b/modules/spdy/support/install/common/mod-spdy.info @@ -0,0 +1,20 @@ +# 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/" diff --git a/modules/spdy/support/install/common/repo.cron b/modules/spdy/support/install/common/repo.cron new file mode 100644 index 00000000000..fbb3e744abc --- /dev/null +++ b/modules/spdy/support/install/common/repo.cron @@ -0,0 +1,42 @@ +#!/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 diff --git a/modules/spdy/support/install/common/rpm.include b/modules/spdy/support/install/common/rpm.include new file mode 100644 index 00000000000..33d33a575f3 --- /dev/null +++ b/modules/spdy/support/install/common/rpm.include @@ -0,0 +1,305 @@ +@@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" < /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 +#} diff --git a/modules/spdy/support/install/common/rpmrepo.cron b/modules/spdy/support/install/common/rpmrepo.cron new file mode 100644 index 00000000000..8b0043a9bf2 --- /dev/null +++ b/modules/spdy/support/install/common/rpmrepo.cron @@ -0,0 +1,56 @@ +#!/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 diff --git a/modules/spdy/support/install/common/spdy.conf.template b/modules/spdy/support/install/common/spdy.conf.template new file mode 100644 index 00000000000..1fc8ad5d52c --- /dev/null +++ b/modules/spdy/support/install/common/spdy.conf.template @@ -0,0 +1,22 @@ + + # 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 + diff --git a/modules/spdy/support/install/common/spdy.load.template b/modules/spdy/support/install/common/spdy.load.template new file mode 100644 index 00000000000..d6e0e55ea5c --- /dev/null +++ b/modules/spdy/support/install/common/spdy.load.template @@ -0,0 +1 @@ +LoadModule spdy_module @@APACHE_MODULEDIR@@/mod_spdy.so diff --git a/modules/spdy/support/install/common/ssl.load.template b/modules/spdy/support/install/common/ssl.load.template new file mode 100644 index 00000000000..8d7e3fd8e34 --- /dev/null +++ b/modules/spdy/support/install/common/ssl.load.template @@ -0,0 +1,15 @@ +# 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 diff --git a/modules/spdy/support/install/common/updater b/modules/spdy/support/install/common/updater new file mode 100755 index 00000000000..53a8a80e954 --- /dev/null +++ b/modules/spdy/support/install/common/updater @@ -0,0 +1,26 @@ +#!/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 + diff --git a/modules/spdy/support/install/common/variables.include b/modules/spdy/support/install/common/variables.include new file mode 100644 index 00000000000..f3a17cd44e1 --- /dev/null +++ b/modules/spdy/support/install/common/variables.include @@ -0,0 +1,5 @@ +# System-wide package configuration. +DEFAULTS_FILE="/etc/default/@@PACKAGE@@" + +# sources.list setting for @@PACKAGE@@ updates. +REPOCONFIG="@@REPOCONFIG@@" diff --git a/modules/spdy/support/install/debian/build.sh b/modules/spdy/support/install/debian/build.sh new file mode 100755 index 00000000000..9feaaa8d4c6 --- /dev/null +++ b/modules/spdy/support/install/debian/build.sh @@ -0,0 +1,249 @@ +#!/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 diff --git a/modules/spdy/support/install/debian/changelog.template b/modules/spdy/support/install/debian/changelog.template new file mode 100644 index 00000000000..4ed22e5974d --- /dev/null +++ b/modules/spdy/support/install/debian/changelog.template @@ -0,0 +1,4 @@ +@@PACKAGE@@-@@CHANNEL@@ (@@VERSIONFULL@@) UNRELEASED; urgency=low + * No changes + + -- @@MAINTNAME@@ <@@MAINTMAIL@@> Wed, 20 Oct 2010 14:54:35 -0800 diff --git a/modules/spdy/support/install/debian/conffiles.template b/modules/spdy/support/install/debian/conffiles.template new file mode 100644 index 00000000000..cc2e7dcba90 --- /dev/null +++ b/modules/spdy/support/install/debian/conffiles.template @@ -0,0 +1,3 @@ +/etc/apache2/mods-available/spdy.load +/etc/apache2/mods-available/spdy.conf +/etc/cron.daily/@@PACKAGE@@ diff --git a/modules/spdy/support/install/debian/control.template b/modules/spdy/support/install/debian/control.template new file mode 100644 index 00000000000..cc9b1d09515 --- /dev/null +++ b/modules/spdy/support/install/debian/control.template @@ -0,0 +1,16 @@ +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@@ diff --git a/modules/spdy/support/install/debian/postinst b/modules/spdy/support/install/debian/postinst new file mode 100755 index 00000000000..9a468af3ddb --- /dev/null +++ b/modules/spdy/support/install/debian/postinst @@ -0,0 +1,67 @@ +#!/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 <&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 diff --git a/modules/spdy/support/install/rpm/build.sh b/modules/spdy/support/install/rpm/build.sh new file mode 100755 index 00000000000..6fec139b1af --- /dev/null +++ b/modules/spdy/support/install/rpm/build.sh @@ -0,0 +1,238 @@ +#!/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" diff --git a/modules/spdy/support/install/rpm/mod-spdy.spec.template b/modules/spdy/support/install/rpm/mod-spdy.spec.template new file mode 100644 index 00000000000..b7a67b35f10 --- /dev/null +++ b/modules/spdy/support/install/rpm/mod-spdy.spec.template @@ -0,0 +1,179 @@ +#------------------------------------------------------------------------------ +# 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 diff --git a/modules/spdy/support/net/net.gyp b/modules/spdy/support/net/net.gyp new file mode 100644 index 00000000000..c82d2243cec --- /dev/null +++ b/modules/spdy/support/net/net.gyp @@ -0,0 +1,51 @@ +# 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', + ], + }, + ], +} diff --git a/modules/spdy/support/scripts/loadtest.py b/modules/spdy/support/scripts/loadtest.py new file mode 100644 index 00000000000..e1fbfeb8f6b --- /dev/null +++ b/modules/spdy/support/scripts/loadtest.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +# Copyright 2012 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A simple script for load-testing mod_spdy (or any other SPDY server). For +# example, to hit the server with 150 simultaneous SPDY clients, each fetching +# the URLs https://example.com/ and https://example.com/image.jpg, you would +# run: +# +# $ ./loadtest.py spdy 150 https://example.com/ https://example.com/image.jpg +# +# To run the same test with plain HTTPS clients instead of SPDY clients (for +# comparison), you would run: +# +# $ ./loadtest.py https 150 https://example.com/ https://example.com/image.jpg +# +# Press Ctrl-C to stop the test. +# +# You must have spdycat (https://github.com/tatsuhiro-t/spdylay) installed and +# on your $PATH in order to run SPDY tests, and you must have curl installed in +# order to run HTTPS or HTTP tests. + +from __future__ import division # Always convert ints to floats for / operator +from __future__ import print_function # Treat print as function, not keyword + +import re +import subprocess +import sys +import time + +#=============================================================================# + +def print_usage_and_quit(): + sys.stderr.write('Usage: {0} TYPE MAX_CLIENTS URL...\n'.format(sys.argv[0])) + sys.stderr.write('TYPE must be one of "spdy", "https", or "http"\n') + sys.stderr.write('MAX_CLIENTS must be a positive integer\n') + sys.exit(1) + +def with_scheme(url, scheme): + """Given a URL string, return a new URL string with the given scheme.""" + if re.match(r'^[a-zA-Z0-9]+:', url): + return re.sub(r'^[a-zA-Z0-9]+:', scheme + ':', url) + elif url.startswith('//'): + return scheme + ':' + url + else: + return scheme + '://' + url + + +class ClientProcess (object): + """A client subprocess that will try to load the URLs from the server.""" + + def __init__(self, key, command, factory): + self.__key = key + self.__child = subprocess.Popen(command, stdout=open('/dev/null', 'wb')) + self.__start_time = time.time() + self.__factory = factory + + def get_key(self): + return self.__key + + def get_start_time(self): + return self.__start_time + + def check_done(self): + """If the client is done, print time and return True, else return False.""" + code = self.__child.poll() + if code is None: + return False + else: + duration = time.time() - self.__start_time + self.__factory._client_finished(self.__key, code, duration) + return True + + def kill(self): + """Shut down this client.""" + self.__child.kill() + + +class ClientFactory (object): + """A factory for ClientProcess objects, that also tracks stats.""" + + def __init__(self, command): + """Create a factory that will use the given command for subprocesses.""" + self.__command = command + self.num_started = 0 + self.num_finished = 0 + self.max_duration = 0.0 + self.total_duration = 0.0 + + def new_client(self): + """Create and return a new ClientProcess.""" + self.num_started += 1 + return ClientProcess(key=self.num_started, command=self.__command, + factory=self) + + def _client_finished(self, key, code, duration): + """Called by each ClientProcess when it finishes.""" + self.num_finished += 1 + self.max_duration = max(self.max_duration, duration) + self.total_duration += duration + print('Client {0} exit {1} after {2:.3f}s'.format(key, code, duration)) + +#=============================================================================# + +if len(sys.argv) < 4: + print_usage_and_quit() + +# Determine what type of test we're doing and what URL scheme to use. +TYPE = sys.argv[1].lower() +if TYPE not in ['spdy', 'https', 'http']: + print_usage_and_quit() +SCHEME = 'https' if TYPE == 'spdy' else TYPE + +# Determine how many clients to have at once. +try: + MAX_CLIENTS = int(sys.argv[2]) +except ValueError: + print_usage_and_quit() +if MAX_CLIENTS < 1: + print_usage_and_quit() + +# Collect the URLs to fetch from. +URLS = [] +for url in sys.argv[3:]: + URLS.append(with_scheme(url, SCHEME)) + +# Put together the subprocess command to issue for each client. +if TYPE == 'spdy': + # The -n flag tells spdycat throw away the downloaded data without saving it. + COMMAND = ['spdycat', '-n'] + URLS +else: + # The -s flag tells curl to be silent (don't display progress meter); the -k + # flag tells curl to ignore certificate errors (e.g. self-signed certs). + COMMAND = ['curl', '-sk'] + URLS + +# Print out a summary of the test we'll be doing before we start. +print('TYPE={0}'.format(TYPE)) +print('URLS ({0}):'.format(len(URLS))) +for url in URLS: + print(' ' + url) +print('MAX_CLIENTS={0}'.format(MAX_CLIENTS)) + +# Run the test. +factory = ClientFactory(COMMAND) +clients = [] +try: + # Start us off with an initial batch of clients. + for index in xrange(MAX_CLIENTS): + clients.append(factory.new_client()) + # Each time a client finishes, replace it with a new client. + # TODO(mdsteele): This is a busy loop, which isn't great. What we want is to + # sleep until one or more children are done. Maybe we could do something + # clever that would allow us to do a select() call here or something. + while True: + for index in xrange(MAX_CLIENTS): + if clients[index].check_done(): + clients[index] = factory.new_client() +# Stop when the user hits Ctrl-C, and print a summary of the results. +except KeyboardInterrupt: + print() + if clients: + slowpoke = min(clients, key=(lambda c: c.get_key())) + print('Earliest unfinished client, {0}, not done after {1:.3f}s'.format( + slowpoke.get_key(), time.time() - slowpoke.get_start_time())) + if factory.num_finished > 0: + print('Avg time per client: {0:.3f}s ({1} started, {2} completed)'.format( + factory.total_duration / factory.num_finished, + factory.num_started, factory.num_finished)) + print('Max time per client: {0:.3f}s'.format(factory.max_duration)) + print("URLs served per second: {0:.3f}".format( + factory.num_finished * len(URLS) / factory.total_duration)) +for client in clients: + client.kill() + +#=============================================================================# diff --git a/modules/spdy/support/scripts/mod_ssl_with_npn.patch b/modules/spdy/support/scripts/mod_ssl_with_npn.patch new file mode 100644 index 00000000000..90119e4a810 --- /dev/null +++ b/modules/spdy/support/scripts/mod_ssl_with_npn.patch @@ -0,0 +1,235 @@ +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 */ diff --git a/modules/spdy/support/third_party/apache/apr/apr.gyp b/modules/spdy/support/third_party/apache/apr/apr.gyp new file mode 100644 index 00000000000..a34695a7649 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/apr.gyp @@ -0,0 +1,212 @@ +# 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 + # 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 + # 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', + ], + }, + }, + ], + }], + ], +} + diff --git a/modules/spdy/support/third_party/apache/apr/gen/arch/linux/ia32/include/apr.h b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/ia32/include/apr.h new file mode 100644 index 00000000000..38c1c1afa52 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/ia32/include/apr.h @@ -0,0 +1,512 @@ +/* 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 + * The actual values of macros and typedefs on this page
+ * are platform specific and should NOT be relied upon!
+ */ + +/* 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 +#endif + +#if APR_HAVE_WINSOCK2_H +#include +#endif + +#if APR_HAVE_SYS_TYPES_H +#include +#endif + +#if APR_HAVE_SYS_SOCKET_H +#include +#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 +#endif + +#if APR_HAVE_SYS_WAIT_H +#include +#endif + +#ifdef OS2 +#define INCL_DOS +#define INCL_DOSERRORS +#include +#endif + +/* header files for PATH_MAX, _POSIX_PATH_MAX */ +#if APR_HAVE_LIMITS_H +#include +#else +#if APR_HAVE_SYS_SYSLIMITS_H +#include +#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. + *
+ *
+ * void* APR_THREAD_FUNC my_thread_entry_fn(apr_thread_t *thd, void *data);
+ *
+ * 
+ */ +#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. + * + *
+ * APR_DECLARE(rettype) apr_func(args)
+ * 
+ * @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. + *
+ *
+ * APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ *
+ * 
+ */ +#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. + * + *
+ *
+ * extern APR_DECLARE_DATA type apr_variable;\n
+ * APR_DECLARE_DATA type apr_variable = value;
+ *
+ * 
+ */ +#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 */ diff --git a/modules/spdy/support/third_party/apache/apr/gen/arch/linux/ia32/include/apr_private.h b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/ia32/include/apr_private.h new file mode 100644 index 00000000000..1716919d621 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/ia32/include/apr_private.h @@ -0,0 +1,938 @@ +/* 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 and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define to 1 if you have the 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 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 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 header file. */ +#define HAVE_CRYPT_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_GRP_H 1 + +/* Define if hstrerror is present */ +/* #undef HAVE_HSTRERROR */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the 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 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 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 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 header file. */ +/* #undef HAVE_MACH_O_DYLD_H */ + +/* Define to 1 if you have the 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 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 header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_SCTP_H */ + +/* Define to 1 if you have the 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 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 header file. */ +/* #undef HAVE_OS2_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OSRELDATE_H */ + +/* Define to 1 if you have the 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 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 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 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 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 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 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 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 header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_SYSAPI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSGTIME_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MUTEX_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SENDFILE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SHM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SOCKIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSCTL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SYSLIMITS_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UUID_H */ + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TPFEQ_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TPFIO_H */ + +/* Define if truerand is supported */ +/* #undef HAVE_TRUERAND */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_UUID_H */ + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the 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 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 does not define. */ +/* #undef off_t */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to `int' if does not define. */ +/* #undef ssize_t */ + +/* Define to `int' if 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 */ diff --git a/modules/spdy/support/third_party/apache/apr/gen/arch/linux/x64/include/apr.h b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/x64/include/apr.h new file mode 100644 index 00000000000..80a8b590063 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/x64/include/apr.h @@ -0,0 +1,512 @@ +/* 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 + * The actual values of macros and typedefs on this page
+ * are platform specific and should NOT be relied upon!
+ */ + +/* 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 +#endif + +#if APR_HAVE_WINSOCK2_H +#include +#endif + +#if APR_HAVE_SYS_TYPES_H +#include +#endif + +#if APR_HAVE_SYS_SOCKET_H +#include +#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 +#endif + +#if APR_HAVE_SYS_WAIT_H +#include +#endif + +#ifdef OS2 +#define INCL_DOS +#define INCL_DOSERRORS +#include +#endif + +/* header files for PATH_MAX, _POSIX_PATH_MAX */ +#if APR_HAVE_LIMITS_H +#include +#else +#if APR_HAVE_SYS_SYSLIMITS_H +#include +#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. + *
+ *
+ * void* APR_THREAD_FUNC my_thread_entry_fn(apr_thread_t *thd, void *data);
+ *
+ * 
+ */ +#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. + * + *
+ * APR_DECLARE(rettype) apr_func(args)
+ * 
+ * @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. + *
+ *
+ * APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ *
+ * 
+ */ +#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. + * + *
+ *
+ * extern APR_DECLARE_DATA type apr_variable;\n
+ * APR_DECLARE_DATA type apr_variable = value;
+ *
+ * 
+ */ +#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 */ diff --git a/modules/spdy/support/third_party/apache/apr/gen/arch/linux/x64/include/apr_private.h b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/x64/include/apr_private.h new file mode 100644 index 00000000000..4feeaa80ab6 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/gen/arch/linux/x64/include/apr_private.h @@ -0,0 +1,938 @@ +/* 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 and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define to 1 if you have the 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 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 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 header file. */ +#define HAVE_CRYPT_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_GRP_H 1 + +/* Define if hstrerror is present */ +/* #undef HAVE_HSTRERROR */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the 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 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 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 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 header file. */ +/* #undef HAVE_MACH_O_DYLD_H */ + +/* Define to 1 if you have the 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 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 header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_SCTP_H */ + +/* Define to 1 if you have the 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 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 header file. */ +/* #undef HAVE_OS2_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OSRELDATE_H */ + +/* Define to 1 if you have the 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 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 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 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 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 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 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 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 header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_SYSAPI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSGTIME_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MUTEX_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SENDFILE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SHM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SOCKIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSCTL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SYSLIMITS_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UUID_H */ + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TPFEQ_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TPFIO_H */ + +/* Define if truerand is supported */ +/* #undef HAVE_TRUERAND */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_UUID_H */ + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the 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 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 does not define. */ +/* #undef off_t */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to `int' if does not define. */ +/* #undef ssize_t */ + +/* Define to `int' if 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 */ diff --git a/modules/spdy/support/third_party/apache/apr/gen/arch/mac/ia32/include/apr.h b/modules/spdy/support/third_party/apache/apr/gen/arch/mac/ia32/include/apr.h new file mode 100644 index 00000000000..47b586dcc63 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/gen/arch/mac/ia32/include/apr.h @@ -0,0 +1,512 @@ +/* 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 + * The actual values of macros and typedefs on this page
+ * are platform specific and should NOT be relied upon!
+ */ + +/* 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 +#endif + +#if APR_HAVE_WINSOCK2_H +#include +#endif + +#if APR_HAVE_SYS_TYPES_H +#include +#endif + +#if APR_HAVE_SYS_SOCKET_H +#include +#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 +#endif + +#if APR_HAVE_SYS_WAIT_H +#include +#endif + +#ifdef OS2 +#define INCL_DOS +#define INCL_DOSERRORS +#include +#endif + +/* header files for PATH_MAX, _POSIX_PATH_MAX */ +#if APR_HAVE_LIMITS_H +#include +#else +#if APR_HAVE_SYS_SYSLIMITS_H +#include +#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. + *
+ *
+ * void* APR_THREAD_FUNC my_thread_entry_fn(apr_thread_t *thd, void *data);
+ *
+ * 
+ */ +#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. + * + *
+ * APR_DECLARE(rettype) apr_func(args)
+ * 
+ * @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. + *
+ *
+ * APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
+ *
+ * 
+ */ +#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. + * + *
+ *
+ * extern APR_DECLARE_DATA type apr_variable;\n
+ * APR_DECLARE_DATA type apr_variable = value;
+ *
+ * 
+ */ +#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 */ diff --git a/modules/spdy/support/third_party/apache/apr/gen/arch/mac/ia32/include/apr_private.h b/modules/spdy/support/third_party/apache/apr/gen/arch/mac/ia32/include/apr_private.h new file mode 100644 index 00000000000..22e1f4319a6 --- /dev/null +++ b/modules/spdy/support/third_party/apache/apr/gen/arch/mac/ia32/include/apr_private.h @@ -0,0 +1,956 @@ +/* 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 and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define to 1 if you have the 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 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 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 header file. */ +/* #undef HAVE_CRYPT_H */ + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_GRP_H 1 + +/* Define if hstrerror is present */ +/* #undef HAVE_HSTRERROR */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the 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 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 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 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 header file. */ +#define HAVE_MACH_O_DYLD_H 1 + +/* Define to 1 if you have the 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 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 header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_SCTP_H */ + +/* Define to 1 if you have the 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 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 header file. */ +/* #undef HAVE_OS2_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OSRELDATE_H */ + +/* Define to 1 if you have the 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 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 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 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 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 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 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 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 header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_SYSAPI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSGTIME_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MUTEX_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SENDFILE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SHM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSLIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UUID_H */ + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TPFEQ_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TPFIO_H */ + +/* Define if truerand is supported */ +/* #undef HAVE_TRUERAND */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_UUID_H */ + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the 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 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 does not define. */ +/* #undef off_t */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to `int' if does not define. */ +/* #undef ssize_t */ + +/* Define to `int' if 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 */ + diff --git a/modules/spdy/support/third_party/apache/aprutil/aprutil.gyp b/modules/spdy/support/third_party/apache/aprutil/aprutil.gyp new file mode 100644 index 00000000000..0a8cb0df805 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/aprutil.gyp @@ -0,0 +1,162 @@ +# 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 + # 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 + # 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', + ], + }, + }, + ], + }], + ], +} + diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apr_ldap.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apr_ldap.h new file mode 100644 index 00000000000..369bdc37004 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apr_ldap.h @@ -0,0 +1,197 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apu.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apu.h new file mode 100644 index 00000000000..2d500e3c93f --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apu.h @@ -0,0 +1,110 @@ +/* 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 */ +/** @} */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apu_want.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apu_want.h new file mode 100644 index 00000000000..25f1100d91c --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/apu_want.h @@ -0,0 +1,51 @@ +/* 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 + * + *
+ * Features:
+ *
+ *   APU_WANT_DB:       
+ *
+ * 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)
+ * 
+ */ + +/* --------------------------------------------------------------------- */ + +#ifdef APU_WANT_DB + +#if APU_HAVE_DB +#include +#endif + +#undef APU_WANT_DB +#endif + +/* --------------------------------------------------------------------- */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/private/apu_config.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/private/apu_config.h new file mode 100644 index 00000000000..062646f297c --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/private/apu_config.h @@ -0,0 +1,149 @@ +/* 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 header file. */ +/* #undef HAVE_FREETDS_SYBDB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ICONV_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LANGINFO_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LBER_H */ + +/* Defined if ldap.h is present */ +/* #undef HAVE_LDAP_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LDAP_SSL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LIBPQ_FE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MYSQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MY_GLOBAL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MY_SYS_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MY_GLOBAL_H */ + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_OCI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ODBC_SQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_POSTGRESQL_LIBPQ_FE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SQLITE3_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SQLITE_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SQL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYBDB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the 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 diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/private/apu_select_dbm.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/private/apu_select_dbm.h new file mode 100644 index 00000000000..1ac89d58490 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/ia32/include/private/apu_select_dbm.h @@ -0,0 +1,28 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apr_ldap.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apr_ldap.h new file mode 100644 index 00000000000..369bdc37004 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apr_ldap.h @@ -0,0 +1,197 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apu.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apu.h new file mode 100644 index 00000000000..2d500e3c93f --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apu.h @@ -0,0 +1,110 @@ +/* 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 */ +/** @} */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apu_want.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apu_want.h new file mode 100644 index 00000000000..25f1100d91c --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/apu_want.h @@ -0,0 +1,51 @@ +/* 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 + * + *
+ * Features:
+ *
+ *   APU_WANT_DB:       
+ *
+ * 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)
+ * 
+ */ + +/* --------------------------------------------------------------------- */ + +#ifdef APU_WANT_DB + +#if APU_HAVE_DB +#include +#endif + +#undef APU_WANT_DB +#endif + +/* --------------------------------------------------------------------- */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/private/apu_config.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/private/apu_config.h new file mode 100644 index 00000000000..062646f297c --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/private/apu_config.h @@ -0,0 +1,149 @@ +/* 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 header file. */ +/* #undef HAVE_FREETDS_SYBDB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ICONV_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LANGINFO_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LBER_H */ + +/* Defined if ldap.h is present */ +/* #undef HAVE_LDAP_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LDAP_SSL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LIBPQ_FE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MYSQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MY_GLOBAL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MY_SYS_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MY_GLOBAL_H */ + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_OCI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ODBC_SQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_POSTGRESQL_LIBPQ_FE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SQLITE3_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SQLITE_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SQL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYBDB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the 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 diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/private/apu_select_dbm.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/private/apu_select_dbm.h new file mode 100644 index 00000000000..1ac89d58490 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/linux/x64/include/private/apu_select_dbm.h @@ -0,0 +1,28 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apr_ldap.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apr_ldap.h new file mode 100644 index 00000000000..369bdc37004 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apr_ldap.h @@ -0,0 +1,197 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apu.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apu.h new file mode 100644 index 00000000000..a49a91a349f --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apu.h @@ -0,0 +1,110 @@ +/* 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 */ +/** @} */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apu_want.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apu_want.h new file mode 100644 index 00000000000..25f1100d91c --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/apu_want.h @@ -0,0 +1,51 @@ +/* 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 + * + *
+ * Features:
+ *
+ *   APU_WANT_DB:       
+ *
+ * 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)
+ * 
+ */ + +/* --------------------------------------------------------------------- */ + +#ifdef APU_WANT_DB + +#if APU_HAVE_DB +#include +#endif + +#undef APU_WANT_DB +#endif + +/* --------------------------------------------------------------------- */ diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/private/apu_config.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/private/apu_config.h new file mode 100644 index 00000000000..4cabe7ed6bb --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/private/apu_config.h @@ -0,0 +1,152 @@ +/* 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 header file. */ +/* #undef HAVE_FREETDS_SYBDB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ICONV_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LANGINFO_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LBER_H */ + +/* Defined if ldap.h is present */ +/* #undef HAVE_LDAP_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LDAP_SSL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LIBPQ_FE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MYSQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MY_GLOBAL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MYSQL_MY_SYS_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MY_GLOBAL_H */ + +/* Define to 1 if you have the 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 header file. */ +/* #undef HAVE_OCI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ODBC_SQL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_POSTGRESQL_LIBPQ_FE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SQLITE3_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SQLITE_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SQL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYBDB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the 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 diff --git a/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/private/apu_select_dbm.h b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/private/apu_select_dbm.h new file mode 100644 index 00000000000..1ac89d58490 --- /dev/null +++ b/modules/spdy/support/third_party/apache/aprutil/gen/arch/mac/ia32/include/private/apu_select_dbm.h @@ -0,0 +1,28 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/ia32/include/ap_config_auto.h b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/ia32/include/ap_config_auto.h new file mode 100644 index 00000000000..ad6606b01dc --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/ia32/include/ap_config_auto.h @@ -0,0 +1,261 @@ +/* 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 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 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 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 header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if SSL is supported using OpenSSL */ +/* #undef HAVE_OPENSSL */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENSSL_ENGINE_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENSSL_OPENSSLV_H */ + +/* Define to 1 if you have the 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 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 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 header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PRCTL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_PROCESSOR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIMES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have 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 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 doesn't define it for us */ +/* #undef rlim_t */ diff --git a/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/ia32/include/ap_config_layout.h b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/ia32/include/ap_config_layout.h new file mode 100644 index 00000000000..d7ee816696b --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/ia32/include/ap_config_layout.h @@ -0,0 +1,66 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/x64/include/ap_config_auto.h b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/x64/include/ap_config_auto.h new file mode 100644 index 00000000000..91ebe0a907f --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/x64/include/ap_config_auto.h @@ -0,0 +1,212 @@ +/* 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 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 header file. */ +/* #undef HAVE_BSTRING_H */ + +/* Define if you have the header file. */ +#define HAVE_GRP_H 1 + +/* Define if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_OPENSSL_ENGINE_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_OPENSSL_OPENSSLV_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_OPENSSL_SSL_H */ + +/* Define if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SSLC_H */ + +/* Define if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_PRCTL_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_PROCESSOR_H */ + +/* Define if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_TIMES_H 1 + +/* Define if you have the 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 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" + diff --git a/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/x64/include/ap_config_layout.h b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/x64/include/ap_config_layout.h new file mode 100644 index 00000000000..d7ee816696b --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/gen/arch/linux/x64/include/ap_config_layout.h @@ -0,0 +1,66 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/httpd/gen/arch/mac/ia32/include/ap_config_auto.h b/modules/spdy/support/third_party/apache/httpd/gen/arch/mac/ia32/include/ap_config_auto.h new file mode 100644 index 00000000000..e4d50496621 --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/gen/arch/mac/ia32/include/ap_config_auto.h @@ -0,0 +1,263 @@ +/* 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 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 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 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 header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if SSL is supported using OpenSSL */ +/* #undef HAVE_OPENSSL */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENSSL_ENGINE_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENSSL_OPENSSLV_H */ + +/* Define to 1 if you have the 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 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 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 header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_PRCTL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_PROCESSOR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIMES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have 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 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 doesn't define it for us */ +/* #undef rlim_t */ diff --git a/modules/spdy/support/third_party/apache/httpd/gen/arch/mac/ia32/include/ap_config_layout.h b/modules/spdy/support/third_party/apache/httpd/gen/arch/mac/ia32/include/ap_config_layout.h new file mode 100644 index 00000000000..a53a73604ee --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/gen/arch/mac/ia32/include/ap_config_layout.h @@ -0,0 +1,66 @@ +/* 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 */ diff --git a/modules/spdy/support/third_party/apache/httpd/httpd.gyp b/modules/spdy/support/third_party/apache/httpd/httpd.gyp new file mode 100644 index 00000000000..82e63034017 --- /dev/null +++ b/modules/spdy/support/third_party/apache/httpd/httpd.gyp @@ -0,0 +1,75 @@ +# 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', + ], + } + ], + }], + ], +} diff --git a/modules/spdy/support/third_party/mod_diagnostics/mod_diagnostics.c b/modules/spdy/support/third_party/mod_diagnostics/mod_diagnostics.c new file mode 100644 index 00000000000..679f718f7cb --- /dev/null +++ b/modules/spdy/support/third_party/mod_diagnostics/mod_diagnostics.c @@ -0,0 +1,222 @@ +/* + mod_diagnostics + + Copyright (C) 2003, Nick Kew + + 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 +#include +#include +#include +#include + +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 diff --git a/modules/spdy/support/third_party/mod_diagnostics/mod_diagnostics.gyp b/modules/spdy/support/third_party/mod_diagnostics/mod_diagnostics.gyp new file mode 100644 index 00000000000..14cf0f865b2 --- /dev/null +++ b/modules/spdy/support/third_party/mod_diagnostics/mod_diagnostics.gyp @@ -0,0 +1,39 @@ +# 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':'', + } + }]], + }, + ], +}