You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2014/05/01 13:39:32 UTC
svn commit: r1591620 [4/14] - in /httpd/mod_spdy/branches/httpd-2.2.x: ./
base/ base/metrics/ build/ install/ install/common/ install/debian/
install/rpm/ mod_spdy/ mod_spdy/apache/ mod_spdy/apache/filters/
mod_spdy/apache/testing/ mod_spdy/common/ mod...
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.cc Thu May 1 11:39:27 2014
@@ -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 <map>
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/spdy_to_http_converter.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// If, during an AP_MODE_GETLINE read, we pull in this much data (or more)
+// without seeing a linebreak, just give up and return what we have.
+const size_t kGetlineThreshold = 4096;
+
+} // namespace
+
+namespace mod_spdy {
+
+SpdyToHttpFilter::SpdyToHttpFilter(SpdyStream* stream)
+ : stream_(stream),
+ visitor_(&data_buffer_),
+ converter_(stream_->spdy_version(), &visitor_),
+ next_read_start_(0) {
+ DCHECK(stream_ != NULL);
+}
+
+SpdyToHttpFilter::~SpdyToHttpFilter() {}
+
+// Macro to check if the SPDY stream has been aborted; if so, mark the
+// connection object as having been aborted and return APR_ECONNABORTED.
+// Hopefully, this will convince Apache to shut down processing for this
+// (slave) connection, thus allowing this stream's thread to complete and exit.
+//
+// As an extra measure, we also insert an EOS bucket into the brigade before
+// returning. This idea comes from ssl_io_filter_input() in ssl_engine_io.c in
+// mod_ssl, which does so with the following comment: "Ok, if we aborted, we
+// ARE at the EOS. We also have aborted. This 'double protection' is probably
+// redundant, but also effective against just about anything."
+#define RETURN_IF_STREAM_ABORT(filter, brigade) \
+ do { \
+ if ((filter)->c->aborted || stream_->is_aborted()) { \
+ (filter)->c->aborted = true; \
+ APR_BRIGADE_INSERT_TAIL( \
+ (brigade), apr_bucket_eos_create((filter)->c->bucket_alloc)); \
+ return APR_ECONNABORTED; \
+ } \
+ } while (false)
+
+apr_status_t SpdyToHttpFilter::Read(ap_filter_t *filter,
+ apr_bucket_brigade *brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes) {
+ // Turn readbytes into a size_t to avoid the need for static_casts below. To
+ // avoid any surprises (in cases where apr_off_t is signed), clamp it to a
+ // non-negative value first.
+ const size_t max_bytes = std::max(static_cast<apr_off_t>(0), readbytes);
+
+ // This is a NETWORK-level filter, so there shouldn't be any filter after us.
+ if (filter->next != NULL) {
+ LOG(WARNING) << "SpdyToHttpFilter is not the last filter in the chain "
+ << "(it is followed by " << filter->next->frec->name << ")";
+ }
+
+ // Clear any buffer data that was already returned on a previous invocation
+ // of this filter.
+ if (next_read_start_ > 0) {
+ data_buffer_.erase(0, next_read_start_);
+ next_read_start_ = 0;
+ }
+
+ // We don't need to do anything for AP_MODE_INIT. (We check this case before
+ // checking for EOF, becuase that's what ap_core_input_filter() in
+ // core_filters.c does.)
+ if (mode == AP_MODE_INIT) {
+ return APR_SUCCESS;
+ }
+
+ // If there will never be any more data on this stream, return EOF. (That's
+ // what ap_core_input_filter() in core_filters.c does.)
+ if (end_of_stream_reached() && data_buffer_.empty()) {
+ return APR_EOF;
+ }
+
+ // Check if this SPDY stream has been aborted, and if so, quit. We will also
+ // check for aborts just after each time we call GetNextFrame (that's a good
+ // time to check, since a stream abort can interrupt a blocking call to
+ // GetNextFrame).
+ RETURN_IF_STREAM_ABORT(filter, brigade);
+
+ // Keep track of how much data, if any, we should place into the brigade.
+ size_t bytes_read = 0;
+
+ // For AP_MODE_READBYTES and AP_MODE_SPECULATIVE, we try to read the quantity
+ // of bytes we are asked for. For AP_MODE_EXHAUSTIVE, we read as much as
+ // possible.
+ if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE ||
+ mode == AP_MODE_EXHAUSTIVE) {
+ // Try to get as much data as we were asked for.
+ while (max_bytes > data_buffer_.size() || mode == AP_MODE_EXHAUSTIVE) {
+ const bool got_frame = GetNextFrame(block);
+ RETURN_IF_STREAM_ABORT(filter, brigade);
+ if (!got_frame) {
+ break;
+ }
+ }
+
+ // Return however much data we read, but no more than they asked for.
+ bytes_read = data_buffer_.size();
+ if (mode != AP_MODE_EXHAUSTIVE && max_bytes < bytes_read) {
+ bytes_read = max_bytes;
+ }
+ }
+ // For AP_MODE_GETLINE, try to return a full text line of data.
+ else if (mode == AP_MODE_GETLINE) {
+ // Try to find the first linebreak in the remaining data stream.
+ size_t linebreak = std::string::npos;
+ size_t start = 0;
+ while (true) {
+ linebreak = data_buffer_.find('\n', start);
+ // Stop if we find a linebreak, or if we've pulled too much data already.
+ if (linebreak != std::string::npos ||
+ data_buffer_.size() >= kGetlineThreshold) {
+ break;
+ }
+ // Remember where we left off so we don't have to re-scan the whole
+ // buffer on the next iteration.
+ start = data_buffer_.size();
+ // We haven't seen a linebreak yet, so try to get more data.
+ const bool got_frame = GetNextFrame(block);
+ RETURN_IF_STREAM_ABORT(filter, brigade);
+ if (!got_frame) {
+ break;
+ }
+ }
+
+ // If we found a linebreak, return data up to and including that linebreak.
+ // Otherwise, just send whatever we were able to get.
+ bytes_read = (linebreak == std::string::npos ?
+ data_buffer_.size() : linebreak + 1);
+ }
+ // We don't support AP_MODE_EATCRLF. Doing so would be tricky, and probably
+ // totally pointless. But if we ever decide to implement it, see
+ // http://mail-archives.apache.org/mod_mbox/httpd-dev/200504.mbox/%3C1e86e5df78f13fcc9af02b3f5d749b33@ricilake.net%3E
+ // for more information on its subtle semantics.
+ else {
+ DCHECK(mode == AP_MODE_EATCRLF);
+ VLOG(2) << "Unsupported read mode (" << mode << ") on stream "
+ << stream_->stream_id();
+ return APR_ENOTIMPL;
+ }
+
+ // Keep track of whether we were able to put any buckets into the brigade.
+ bool success = false;
+
+ // If we managed to read any data, put it into the brigade. We use a
+ // transient bucket (as opposed to a heap bucket) to avoid an extra string
+ // copy.
+ if (bytes_read > 0) {
+ APR_BRIGADE_INSERT_TAIL(brigade, apr_bucket_transient_create(
+ data_buffer_.data(), bytes_read, brigade->bucket_alloc));
+ success = true;
+ }
+
+ // If this is the last bit of data from this stream, send an EOS bucket.
+ if (end_of_stream_reached() && bytes_read == data_buffer_.size()) {
+ APR_BRIGADE_INSERT_TAIL(brigade, apr_bucket_eos_create(
+ brigade->bucket_alloc));
+ success = true;
+ }
+
+ // If this read failed and this was a non-blocking read, invite the caller to
+ // try again.
+ if (!success && block == APR_NONBLOCK_READ) {
+ return APR_EAGAIN;
+ }
+
+ // Unless this is a speculative read, we should skip past the bytes we read
+ // next time this filter is invoked. We don't want to erase those bytes
+ // yet, though, so that we can return them to the previous filter in a
+ // transient bucket.
+ if (mode != AP_MODE_SPECULATIVE) {
+ next_read_start_ = bytes_read;
+ }
+
+ return APR_SUCCESS;
+}
+
+SpdyToHttpFilter::DecodeFrameVisitor::DecodeFrameVisitor(
+ SpdyToHttpFilter* filter)
+ : filter_(filter), success_(false) {
+ DCHECK(filter_);
+}
+
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitSynStream(
+ const net::SpdySynStreamIR& frame) {
+ success_ = filter_->DecodeSynStreamFrame(frame);
+}
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitSynReply(
+ const net::SpdySynReplyIR& frame) { BadFrameType("SYN_REPLY"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitRstStream(
+ const net::SpdyRstStreamIR& frame) { BadFrameType("RST_STREAM"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitSettings(
+ const net::SpdySettingsIR& frame) { BadFrameType("SETTINGS"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitPing(
+ const net::SpdyPingIR& frame) { BadFrameType("PING"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitGoAway(
+ const net::SpdyGoAwayIR& frame) { BadFrameType("GOAWAY"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitHeaders(
+ const net::SpdyHeadersIR& frame) {
+ success_ = filter_->DecodeHeadersFrame(frame);
+}
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitWindowUpdate(
+ const net::SpdyWindowUpdateIR& frame) { BadFrameType("WINDOW_UPDATE"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitCredential(
+ const net::SpdyCredentialIR& frame) { BadFrameType("CREDENTIAL"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitBlocked(
+ const net::SpdyBlockedIR& frame) { BadFrameType("BLOCKED"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitPushPromise(
+ const net::SpdyPushPromiseIR& frame) { BadFrameType("PUSH_PROMISE"); }
+void SpdyToHttpFilter::DecodeFrameVisitor::VisitData(
+ const net::SpdyDataIR& frame) {
+ success_ = filter_->DecodeDataFrame(frame);
+}
+
+void SpdyToHttpFilter::DecodeFrameVisitor::BadFrameType(
+ const char* frame_type) {
+ LOG(DFATAL) << "Master connection sent a " << frame_type
+ << " frame to stream " << filter_->stream_->stream_id();
+ filter_->AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ success_ = false;
+}
+
+bool SpdyToHttpFilter::GetNextFrame(apr_read_type_e block) {
+ if (end_of_stream_reached()) {
+ return false;
+ }
+
+ // Try to get the next SPDY frame from the stream.
+ scoped_ptr<net::SpdyFrameIR> frame;
+ {
+ net::SpdyFrameIR* frame_ptr = NULL;
+ if (!stream_->GetInputFrame(block == APR_BLOCK_READ, &frame_ptr)) {
+ DCHECK(frame_ptr == NULL);
+ return false;
+ }
+ frame.reset(frame_ptr);
+ }
+ DCHECK(frame.get() != NULL);
+
+ // Decode the frame into HTTP and append to the data buffer.
+ DecodeFrameVisitor visitor(this);
+ frame->Visit(&visitor);
+ return visitor.success();
+}
+
+bool SpdyToHttpFilter::DecodeSynStreamFrame(
+ const net::SpdySynStreamIR& frame) {
+ const SpdyToHttpConverter::Status status =
+ converter_.ConvertSynStreamFrame(frame);
+ switch (status) {
+ case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+ return true;
+ case SpdyToHttpConverter::EXTRA_SYN_STREAM:
+ // If we get multiple SYN_STREAM frames for a stream, we must abort
+ // with PROTOCOL_ERROR (SPDY draft 2 section 2.7.1).
+ LOG(ERROR) << "Client sent extra SYN_STREAM frame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_PROTOCOL_ERROR);
+ return false;
+ case SpdyToHttpConverter::INVALID_HEADER_BLOCK:
+ LOG(ERROR) << "Invalid SYN_STREAM header block on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_PROTOCOL_ERROR);
+ return false;
+ case SpdyToHttpConverter::BAD_REQUEST:
+ // TODO(mdsteeele): According to the SPDY spec, we're supposed to return
+ // an HTTP 400 (Bad Request) reply in this case (SPDY draft 3 section
+ // 3.2.1). We need to do some refactoring to make that possible.
+ LOG(ERROR) << "Could not generate request line from SYN_STREAM frame"
+ << " in stream " << stream_->stream_id();
+ AbortStream(net::RST_STREAM_REFUSED_STREAM);
+ return false;
+ default:
+ // No other outcome should be possible.
+ LOG(DFATAL) << "Got " << SpdyToHttpConverter::StatusString(status)
+ << " from ConvertSynStreamFrame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ return false;
+ }
+}
+
+bool SpdyToHttpFilter::DecodeHeadersFrame(const net::SpdyHeadersIR& frame) {
+ const SpdyToHttpConverter::Status status =
+ converter_.ConvertHeadersFrame(frame);
+ switch (status) {
+ case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+ return true;
+ case SpdyToHttpConverter::FRAME_AFTER_FIN:
+ AbortStream(net::RST_STREAM_INVALID_STREAM);
+ return false;
+ case SpdyToHttpConverter::INVALID_HEADER_BLOCK:
+ LOG(ERROR) << "Invalid HEADERS header block on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_PROTOCOL_ERROR);
+ return false;
+ default:
+ // No other outcome should be possible.
+ LOG(DFATAL) << "Got " << SpdyToHttpConverter::StatusString(status)
+ << " from ConvertHeadersFrame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ return false;
+ }
+}
+
+bool SpdyToHttpFilter::DecodeDataFrame(const net::SpdyDataIR& frame) {
+ const SpdyToHttpConverter::Status status =
+ converter_.ConvertDataFrame(frame);
+ switch (status) {
+ case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+ // TODO(mdsteele): This isn't really the ideal place for this -- we
+ // shouldn't send the WINDOW_UPDATE until we're about to return the
+ // data to the previous filter, so that we're aren't buffering an
+ // unbounded amount of data in this filter. The trouble is that once
+ // we convert the frames, everything goes into data_buffer_ and we
+ // forget which of it is leading/trailing headers and which of it is
+ // request data, so it'll take a little work to know when to send the
+ // WINDOW_UPDATE frames. For now, just doing it here is good enough.
+ stream_->OnInputDataConsumed(frame.data().size());
+ return true;
+ case SpdyToHttpConverter::FRAME_AFTER_FIN:
+ // If the stream is no longer open, we must send a RST_STREAM with
+ // INVALID_STREAM (SPDY draft 3 section 2.2.2).
+ AbortStream(net::RST_STREAM_INVALID_STREAM);
+ return false;
+ default:
+ // No other outcome should be possible.
+ LOG(DFATAL) << "Got " << SpdyToHttpConverter::StatusString(status)
+ << " from ConvertDataFrame on stream "
+ << stream_->stream_id();
+ AbortStream(net::RST_STREAM_INTERNAL_ERROR);
+ return false;
+ }
+}
+
+void SpdyToHttpFilter::AbortStream(net::SpdyRstStreamStatus status) {
+ stream_->AbortWithRstStream(status);
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.h Thu May 1 11:39:27 2014
@@ -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 <string>
+
+#include "apr_buckets.h"
+#include "util_filter.h"
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/http_string_builder.h"
+#include "mod_spdy/common/spdy_to_http_converter.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// An Apache filter for pulling SPDY frames (for a single SPDY stream) from the
+// input queue of a SpdyStream object and converting them into equivalent HTTP
+// data to be processed by Apache. This is intended to be the outermost filter
+// in the input chain of one of our slave connections, essentially taking the
+// place of the network socket.
+class SpdyToHttpFilter {
+ public:
+ explicit SpdyToHttpFilter(SpdyStream* stream);
+ ~SpdyToHttpFilter();
+
+ apr_status_t Read(ap_filter_t* filter,
+ apr_bucket_brigade* brigade,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes);
+
+ private:
+ friend class DecodeFrameVisitor;
+ class DecodeFrameVisitor : public net::SpdyFrameVisitor {
+ public:
+ explicit DecodeFrameVisitor(SpdyToHttpFilter* filter);
+ virtual ~DecodeFrameVisitor() {}
+
+ bool success() { return success_; }
+
+ virtual void VisitSynStream(const net::SpdySynStreamIR& frame);
+ virtual void VisitSynReply(const net::SpdySynReplyIR& frame);
+ virtual void VisitRstStream(const net::SpdyRstStreamIR& frame);
+ virtual void VisitSettings(const net::SpdySettingsIR& frame);
+ virtual void VisitPing(const net::SpdyPingIR& frame);
+ virtual void VisitGoAway(const net::SpdyGoAwayIR& frame);
+ virtual void VisitHeaders(const net::SpdyHeadersIR& frame);
+ virtual void VisitWindowUpdate(const net::SpdyWindowUpdateIR& frame);
+ virtual void VisitCredential(const net::SpdyCredentialIR& frame);
+ virtual void VisitBlocked(const net::SpdyBlockedIR& frame);
+ virtual void VisitPushPromise(const net::SpdyPushPromiseIR& frame);
+ virtual void VisitData(const net::SpdyDataIR& frame);
+
+ private:
+ void BadFrameType(const char* frame_type);
+
+ SpdyToHttpFilter* filter_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecodeFrameVisitor);
+ };
+
+ // Return true if we've received a FLAG_FIN (i.e. EOS has been reached).
+ bool end_of_stream_reached() const { return visitor_.is_complete(); }
+
+ // Try to get the next SPDY frame on this stream, convert it into HTTP, and
+ // append the resulting data to data_buffer_. If the block argument is
+ // APR_BLOCK_READ, this function will block until a frame comes in (or the
+ // stream is closed).
+ bool GetNextFrame(apr_read_type_e block);
+
+ // Pass the given frame to the SpdyToHttpConverter, and deal with the return
+ // code appropriately.
+ bool DecodeSynStreamFrame(const net::SpdySynStreamIR& frame);
+ bool DecodeHeadersFrame(const net::SpdyHeadersIR& frame);
+ bool DecodeDataFrame(const net::SpdyDataIR& frame);
+
+ // Send a RST_STREAM frame and abort the stream.
+ void AbortStream(net::SpdyRstStreamStatus status);
+
+ SpdyStream* const stream_;
+ std::string data_buffer_;
+ HttpStringBuilder visitor_;
+ SpdyToHttpConverter converter_;
+ size_t next_read_start_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyToHttpFilter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_FILTERS_SPDY_TO_HTTP_FILTER_H_
Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/filters/spdy_to_http_filter_test.cc Thu May 1 11:39:27 2014
@@ -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 <string>
+
+#include "httpd.h"
+#include "apr_buckets.h"
+#include "apr_tables.h"
+#include "util_filter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+ MOCK_METHOD4(StartServerPush,
+ mod_spdy::SpdyServerPushInterface::PushStatus(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyNameValueBlock& request_headers));
+};
+
+class SpdyToHttpFilterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ SpdyToHttpFilterTest()
+ : spdy_version_(GetParam()),
+ stream_id_(1),
+ priority_(0u),
+ shared_window_(net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize),
+ stream_(spdy_version_, stream_id_, 0, 0, priority_,
+ net::kSpdyStreamInitialWindowSize, &output_queue_,
+ &shared_window_, &pusher_),
+ spdy_to_http_filter_(&stream_) {
+ bucket_alloc_ = apr_bucket_alloc_create(local_.pool());
+ connection_ = static_cast<conn_rec*>(
+ apr_pcalloc(local_.pool(), sizeof(conn_rec)));
+ connection_->pool = local_.pool();
+ connection_->bucket_alloc = bucket_alloc_;
+ ap_filter_ = static_cast<ap_filter_t*>(
+ apr_pcalloc(local_.pool(), sizeof(ap_filter_t)));
+ ap_filter_->c = connection_;
+ brigade_ = apr_brigade_create(local_.pool(), bucket_alloc_);
+ }
+
+ protected:
+ void PostSynStreamFrame(bool fin, const net::SpdyNameValueBlock& headers) {
+ scoped_ptr<net::SpdySynStreamIR> frame(
+ new net::SpdySynStreamIR(stream_id_));
+ frame->set_priority(priority_);
+ frame->set_fin(fin);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ stream_.PostInputFrame(frame.release());
+ }
+
+ void PostHeadersFrame(bool fin, const net::SpdyNameValueBlock& headers) {
+ scoped_ptr<net::SpdyHeadersIR> frame(new net::SpdyHeadersIR(stream_id_));
+ frame->set_fin(fin);
+ frame->GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ stream_.PostInputFrame(frame.release());
+ }
+
+ void PostDataFrame(bool fin, const base::StringPiece& payload) {
+ scoped_ptr<net::SpdyDataIR> frame(
+ new net::SpdyDataIR(stream_id_, payload));
+ frame->set_fin(fin);
+ EXPECT_TRUE(shared_window_.OnReceiveInputData(payload.size()));
+ stream_.PostInputFrame(frame.release());
+ }
+
+ apr_status_t Read(ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t readbytes) {
+ return spdy_to_http_filter_.Read(ap_filter_, brigade_,
+ mode, block, readbytes);
+ }
+
+ void ExpectTransientBucket(const std::string& expected) {
+ ASSERT_FALSE(APR_BRIGADE_EMPTY(brigade_))
+ << "Expected TRANSIENT bucket, but brigade is empty.";
+ apr_bucket* bucket = APR_BRIGADE_FIRST(brigade_);
+ ASSERT_TRUE(APR_BUCKET_IS_TRANSIENT(bucket))
+ << "Expected TRANSIENT bucket, but found " << bucket->type->name
+ << " bucket.";
+ const char* data = NULL;
+ apr_size_t size = 0;
+ ASSERT_EQ(APR_SUCCESS, apr_bucket_read(
+ bucket, &data, &size, APR_NONBLOCK_READ));
+ EXPECT_EQ(expected, std::string(data, size));
+ apr_bucket_delete(bucket);
+ }
+
+ void ExpectEosBucket() {
+ ASSERT_FALSE(APR_BRIGADE_EMPTY(brigade_))
+ << "Expected EOS bucket, but brigade is empty.";
+ apr_bucket* bucket = APR_BRIGADE_FIRST(brigade_);
+ ASSERT_TRUE(APR_BUCKET_IS_EOS(bucket))
+ << "Expected EOS bucket, but found " << bucket->type->name
+ << " bucket.";
+ apr_bucket_delete(bucket);
+ }
+
+ void ExpectEndOfBrigade() {
+ ASSERT_TRUE(APR_BRIGADE_EMPTY(brigade_))
+ << "Expected brigade to be empty, but found "
+ << APR_BRIGADE_FIRST(brigade_)->type->name << " bucket.";
+ ASSERT_EQ(APR_SUCCESS, apr_brigade_cleanup(brigade_));
+ }
+
+ void ExpectRstStream(net::SpdyRstStreamStatus status) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue_.Pop(&raw_frame))
+ << "Expected RST_STREAM frame, but output queue is empty.";
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, mod_spdy::testing::IsRstStream(stream_id_, status));
+ }
+
+ void ExpectNoMoreOutputFrames() {
+ EXPECT_TRUE(output_queue_.IsEmpty());
+ }
+
+ bool is_spdy2() const { return GetParam() < mod_spdy::spdy::SPDY_VERSION_3; }
+
+ const char* host_header_name() const {
+ return is_spdy2() ? mod_spdy::http::kHost : mod_spdy::spdy::kSpdy3Host;
+ }
+ const char* method_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Method :
+ mod_spdy::spdy::kSpdy3Method);
+ }
+ const char* path_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Url :
+ mod_spdy::spdy::kSpdy3Path);
+ }
+ const char* scheme_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Scheme :
+ mod_spdy::spdy::kSpdy3Scheme);
+ }
+ const char* version_header_name() const {
+ return (is_spdy2() ? mod_spdy::spdy::kSpdy2Version :
+ mod_spdy::spdy::kSpdy3Version);
+ }
+
+ const mod_spdy::spdy::SpdyVersion spdy_version_;
+ const net::SpdyStreamId stream_id_;
+ const net::SpdyPriority priority_;
+ mod_spdy::SpdyFramePriorityQueue output_queue_;
+ mod_spdy::SharedFlowControlWindow shared_window_;
+ MockSpdyServerPushInterface pusher_;
+ mod_spdy::SpdyStream stream_;
+ mod_spdy::SpdyToHttpFilter spdy_to_http_filter_;
+
+ mod_spdy::LocalPool local_;
+ apr_bucket_alloc_t* bucket_alloc_;
+ conn_rec* connection_;
+ ap_filter_t* ap_filter_;
+ apr_bucket_brigade* brigade_;
+};
+
+TEST_P(SpdyToHttpFilterTest, SimpleGetRequest) {
+ // Perform an INIT. It should succeed, with no effect.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_INIT, APR_BLOCK_READ, 1337));
+ ExpectEndOfBrigade();
+
+ // Invoke the fitler in non-blocking GETLINE mode. We shouldn't get anything
+ // yet, because we haven't sent any frames from the client yet.
+ ASSERT_TRUE(APR_STATUS_IS_EAGAIN(
+ Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0)));
+ ExpectEndOfBrigade();
+
+ // Send a SYN_STREAM frame from the client, with FLAG_FIN set.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.com";
+ headers[method_header_name()] = "GET";
+ headers["referer"] = "https://www.example.com/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/foo/bar/index.html";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ headers["x-do-not-track"] = "1";
+ PostSynStreamFrame(true, headers);
+
+ // Invoke the filter in blocking GETLINE mode. We should get back just the
+ // HTTP request line.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_BLOCK_READ, 0));
+ ExpectTransientBucket("GET /foo/bar/index.html HTTP/1.1\r\n");
+ ExpectEndOfBrigade();
+
+ // Now do a SPECULATIVE read. We should get back a few bytes.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_SPECULATIVE, APR_NONBLOCK_READ, 8));
+ ExpectTransientBucket("host: ww");
+ ExpectEndOfBrigade();
+
+ // Now do another GETLINE read. We should get back the first header line,
+ // including the data we just read speculatively.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("host: www.example.com\r\n");
+ ExpectEndOfBrigade();
+
+ // Do a READBYTES read. We should get back a few bytes.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 12));
+ ExpectTransientBucket("referer: htt");
+ ExpectEndOfBrigade();
+
+ // Do another GETLINE read. We should get back the rest of the header line,
+ // *not* including the data we just read.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("ps://www.example.com/index.html\r\n");
+ ExpectEndOfBrigade();
+
+ // Finally, do an EXHAUSTIVE read. We should get back everything that
+ // remains, terminating with an EOS bucket.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("user-agent: ModSpdyUnitTest/1.0\r\n"
+ "x-do-not-track: 1\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+
+ // There's no more data left; attempting another read should result in EOF.
+ ASSERT_TRUE(APR_STATUS_IS_EOF(
+ Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 4)));
+}
+
+TEST_P(SpdyToHttpFilterTest, SimplePostRequest) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.com";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.com/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/erase/the/whole/database.cgi";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Do a nonblocking READBYTES read. We ask for lots of bytes, but since it's
+ // nonblocking we should immediately get back what's available so far.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 4096));
+ ExpectTransientBucket("POST /erase/the/whole/database.cgi HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "referer: https://www.example.com/index.html\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n");
+ ExpectEndOfBrigade();
+
+ // There's nothing more available yet, so a nonblocking read should fail.
+ ASSERT_TRUE(APR_STATUS_IS_EAGAIN(
+ Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 4)));
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+
+ // Send some DATA frames.
+ PostDataFrame(false, "Hello, world!\nPlease erase ");
+ PostDataFrame(false, "the whole database ");
+ PostDataFrame(true, "immediately.\nThanks!\n");
+
+ // Now read in the data a bit at a time.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("transfer-encoding: chunked\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("accept-encoding: gzip,deflate\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("1B\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 24));
+ ExpectTransientBucket("Hello, world!\nPlease era");
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_SPECULATIVE, APR_NONBLOCK_READ, 15));
+ ExpectTransientBucket("se \r\n13\r\nthe wh");
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 36));
+ ExpectTransientBucket("se \r\n13\r\nthe whole database \r\n15\r\nim");
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_READBYTES, APR_NONBLOCK_READ, 21));
+ ExpectTransientBucket("mediately.\nThanks!\n\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("0\r\n");
+ ExpectEndOfBrigade();
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_GETLINE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+
+ // There's no more data left; attempting another read should result in EOF.
+ ASSERT_TRUE(APR_STATUS_IS_EOF(Read(AP_MODE_GETLINE, APR_BLOCK_READ, 0)));
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithHeadersFrames) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.net";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.net/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/erase/the/whole/database.cgi";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Send some DATA and HEADERS frames. The HEADERS frames should get buffered
+ // and placed at the end of the HTTP request body as trailing headers.
+ PostDataFrame(false, "Please erase ");
+ net::SpdyNameValueBlock headers2;
+ headers2["x-super-cool"] = "foo";
+ PostHeadersFrame(false, headers2);
+ PostDataFrame(false, "everything ");
+ net::SpdyNameValueBlock headers3;
+ headers3["x-awesome"] = "quux";
+ PostHeadersFrame(false, headers3);
+ PostDataFrame(true, "immediately!!\n");
+
+ // Read in all the data.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /erase/the/whole/database.cgi HTTP/1.1\r\n"
+ "host: www.example.net\r\n"
+ "referer: https://www.example.net/index.html\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "D\r\n"
+ "Please erase \r\n"
+ "B\r\n"
+ "everything \r\n"
+ "E\r\n"
+ "immediately!!\n\r\n"
+ "0\r\n"
+ "x-awesome: quux\r\n"
+ "x-super-cool: foo\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, GetRequestWithHeadersRightAfterSynStream) {
+ // Send a SYN_STREAM frame with some of the headers.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "GET";
+ headers["referer"] = "https://www.example.org/foo/bar.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/index.html";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Read in everything that's available so far.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("GET /index.html HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/foo/bar.html\r\n");
+ ExpectEndOfBrigade();
+
+ // Send a HEADERS frame with the rest of the headers.
+ net::SpdyNameValueBlock headers2;
+ headers2["accept-encoding"] = "deflate, gzip";
+ headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+ PostHeadersFrame(true, headers2);
+
+ // Read in the rest of the request.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("accept-encoding: deflate, gzip\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithHeadersRightAfterSynStream) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/delete/everything.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ headers["x-zzzz"] = "4Z";
+ PostSynStreamFrame(false, headers);
+
+ // Send a HEADERS frame before sending any data frames.
+ net::SpdyNameValueBlock headers2;
+ headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+ PostHeadersFrame(false, headers2);
+
+ // Now send a couple DATA frames and a final HEADERS frame.
+ PostDataFrame(false, "Please erase everything immediately");
+ PostDataFrame(false, ", thanks!\n");
+ net::SpdyNameValueBlock headers3;
+ headers3["x-qqq"] = "3Q";
+ PostHeadersFrame(true, headers3);
+
+ // Read in all the data. The first HEADERS frame should get put in before
+ // the data, and the last HEADERS frame should get put in after the data.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /delete/everything.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "x-zzzz: 4Z\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "23\r\n"
+ "Please erase everything immediately\r\n"
+ "A\r\n"
+ ", thanks!\n\r\n"
+ "0\r\n"
+ "x-qqq: 3Q\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithEmptyDataFrameInMiddle) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Now send a few DATA frames, with a zero-length data frame in the middle.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(false, "");
+ PostDataFrame(true, "stuff.\n");
+
+ // Read in all the data. The empty data frame should be ignored.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "9\r\n"
+ "Please do\r\n"
+ "6\r\n"
+ " some \r\n"
+ "7\r\n"
+ "stuff.\n\r\n"
+ "0\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithEmptyDataFrameAtEnd) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Now send a few DATA frames, with a zero-length data frame at the end.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(false, "stuff.\n");
+ PostDataFrame(true, "");
+
+ // Read in all the data. The empty data frame should be ignored (except for
+ // its FLAG_FIN).
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "transfer-encoding: chunked\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "9\r\n"
+ "Please do\r\n"
+ "6\r\n"
+ " some \r\n"
+ "7\r\n"
+ "stuff.\n\r\n"
+ "0\r\n"
+ "\r\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithContentLength) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Send a few more headers before sending data, including a content-length.
+ net::SpdyNameValueBlock headers2;
+ headers2["content-length"] = "22";
+ headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+ PostHeadersFrame(false, headers2);
+
+ // Now send a few DATA frames.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(true, "stuff.\n");
+
+ // Read in all the data. Because we supplied a content-length, chunked
+ // encoding should not be used (to support modules that don't work with
+ // chunked requests).
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "content-length: 22\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "Please do some stuff.\n");
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, PostRequestWithContentLengthAndTrailingHeaders) {
+ // Send a SYN_STREAM frame from the client, including a content-length.
+ net::SpdyNameValueBlock headers;
+ headers["content-length"] = "22";
+ headers[host_header_name()] = "www.example.org";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.org/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/do/some/stuff.py";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Now send a few DATA frames.
+ PostDataFrame(false, "Please do");
+ PostDataFrame(false, " some ");
+ PostDataFrame(false, "stuff.\n");
+
+ // Finish with a HEADERS frame.
+ net::SpdyNameValueBlock headers2;
+ headers2["x-metadata"] = "foobar";
+ headers2["x-whatever"] = "quux";
+ PostHeadersFrame(true, headers2);
+
+ // Read in all the data. Because we supplied a content-length, chunked
+ // encoding should not be used, and as an unfortunate consequence, we must
+ // therefore ignore the trailing headers (justified in that, at least in
+ // HTTP, they're generally only used for ignorable metadata; in fact, they're
+ // not generally used at all).
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ // One (usually irrelevant) quirk of our implementation is that the host
+ // header appears in a slightly different place for SPDY v2 and SPDY v3.
+ // This is beacuse in SPDY v3 the host header is ":host", which sorts
+ // earlier, and which we transform into the HTTP header "host".
+ if (is_spdy2()) {
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "content-length: 22\r\n"
+ "host: www.example.org\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "Please do some stuff.\n");
+ } else {
+ ExpectTransientBucket("POST /do/some/stuff.py HTTP/1.1\r\n"
+ "host: www.example.org\r\n"
+ "content-length: 22\r\n"
+ "referer: https://www.example.org/index.html\r\n"
+ "accept-encoding: gzip,deflate\r\n"
+ "\r\n"
+ "Please do some stuff.\n");
+ }
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ ExpectNoMoreOutputFrames();
+}
+
+TEST_P(SpdyToHttpFilterTest, ExtraSynStream) {
+ // Send a SYN_STREAM frame from the client.
+ net::SpdyNameValueBlock headers;
+ headers[host_header_name()] = "www.example.com";
+ headers[method_header_name()] = "POST";
+ headers["referer"] = "https://www.example.com/index.html";
+ headers[scheme_header_name()] = "https";
+ headers[path_header_name()] = "/erase/the/whole/database.cgi";
+ headers["user-agent"] = "ModSpdyUnitTest/1.0";
+ headers[version_header_name()] = "HTTP/1.1";
+ PostSynStreamFrame(false, headers);
+
+ // Read in all available data.
+ ASSERT_EQ(APR_SUCCESS, Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0));
+ ExpectTransientBucket("POST /erase/the/whole/database.cgi HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "referer: https://www.example.com/index.html\r\n"
+ "user-agent: ModSpdyUnitTest/1.0\r\n");
+ ExpectEndOfBrigade();
+
+ // Now send another SYN_STREAM for the same stream_id, which is illegal.
+ PostSynStreamFrame(false, headers);
+ // If we try to read more data, we'll get nothing.
+ ASSERT_TRUE(APR_STATUS_IS_ECONNABORTED(
+ Read(AP_MODE_EXHAUSTIVE, APR_NONBLOCK_READ, 0)));
+ ExpectEosBucket();
+ ExpectEndOfBrigade();
+ // The stream should have been aborted.
+ ExpectRstStream(net::RST_STREAM_PROTOCOL_ERROR);
+ ExpectNoMoreOutputFrames();
+ EXPECT_TRUE(stream_.is_aborted());
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, SpdyToHttpFilterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.cc Thu May 1 11:39:27 2014
@@ -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 <vector>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace mod_spdy {
+
+IdPool* IdPool::g_instance = NULL;
+const uint16 IdPool::kOverFlowId;
+
+IdPool::IdPool()
+ : next_never_used_(0) /* So it gets incremented to 1 in ::Alloc */ {
+}
+
+IdPool::~IdPool() {
+}
+
+void IdPool::CreateInstance() {
+ DCHECK(g_instance == NULL);
+ g_instance = new IdPool();
+}
+
+void IdPool::DestroyInstance() {
+ DCHECK(g_instance != NULL);
+ delete g_instance;
+ g_instance = NULL;
+}
+
+uint16 IdPool::Alloc() {
+ base::AutoLock lock(mutex_);
+ if (!free_list_.empty()) {
+ uint16 id = free_list_.back();
+ free_list_.pop_back();
+ alloc_set_.insert(id);
+ return id;
+ }
+
+ // We do not use 0 or kOverFlowId normally..
+ if (alloc_set_.size() == (0x10000 - 2)) {
+ LOG(WARNING) << "Out of slave fetch IDs, things may break";
+ return kOverFlowId;
+ }
+
+ // Freelist is empty, but we haven't yet used some ID, so return it.
+ ++next_never_used_;
+ DCHECK(next_never_used_ != kOverFlowId);
+ DCHECK(alloc_set_.find(next_never_used_) == alloc_set_.end());
+ alloc_set_.insert(next_never_used_);
+ return next_never_used_;
+}
+
+void IdPool::Free(uint16 id) {
+ if (id == kOverFlowId) {
+ return;
+ }
+
+ base::AutoLock lock(mutex_);
+ DCHECK(alloc_set_.find(id) != alloc_set_.end());
+ alloc_set_.erase(id);
+ free_list_.push_back(id);
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.h Thu May 1 11:39:27 2014
@@ -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 <vector>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+
+namespace mod_spdy {
+
+// A class for managing non-zero 16-bit process-global IDs.
+class IdPool {
+ public:
+ static const uint16 kOverFlowId = 0xFFFF;
+
+ // Returns the one and only instance of the IdPool. Note that one must
+ // be created with CreateInstance().
+ static IdPool* Instance() { return g_instance; }
+
+ // Call this before threading starts to initialize the instance pointer.
+ static void CreateInstance();
+
+ // Call this once you're done with the pool object to delete it.
+ static void DestroyInstance();
+
+ // Allocates a new, distinct, non-zero ID. 2^16-2 possible values may be
+ // returned; if more than that are needed simultaneously (without being
+ // Free()d) kOverFlowId will always be returned.
+ uint16 Alloc();
+
+ // Release an ID that's no longer in use, making it available for further
+ // calls to Alloc().
+ void Free(uint16 id);
+
+ private:
+ IdPool();
+ ~IdPool();
+
+ static IdPool* g_instance;
+
+ base::Lock mutex_;
+ std::vector<uint16> free_list_; // IDs known to be free
+ std::set<uint16> alloc_set_; // IDs currently in use
+ uint16 next_never_used_; // Next ID we have never returned from Alloc,
+ // for use when the free list is empty.
+
+ DISALLOW_COPY_AND_ASSIGN(IdPool);
+};
+
+} // namespace mod_spdy
+
+#endif /* MOD_SPDY_APACHE_ID_POOL_H_ */
Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/id_pool_test.cc Thu May 1 11:39:27 2014
@@ -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 <set>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using mod_spdy::IdPool;
+
+TEST(IdPoolTest, Lifetime) {
+ EXPECT_EQ(NULL, IdPool::Instance());
+ IdPool::CreateInstance();
+ EXPECT_TRUE(IdPool::Instance() != NULL);
+ IdPool::DestroyInstance();
+ EXPECT_EQ(NULL, IdPool::Instance());
+}
+
+TEST(IdPoolTest, BasicAllocation) {
+ IdPool::CreateInstance();
+ IdPool* instance = IdPool::Instance();
+ uint16 id_1 = instance->Alloc();
+ uint16 id_2 = instance->Alloc();
+ uint16 id_3 = instance->Alloc();
+ EXPECT_NE(0, id_1);
+ EXPECT_NE(0, id_2);
+ EXPECT_NE(0, id_3);
+ EXPECT_NE(id_1, id_2);
+ EXPECT_NE(id_1, id_3);
+ EXPECT_NE(id_2, id_3);
+ instance->Free(id_1);
+ instance->Free(id_2);
+ instance->Free(id_3);
+ IdPool::DestroyInstance();
+}
+
+TEST(IdPoolTest, AllocatingMany) {
+ // We should be able to allocate 2^16-2 unique ids.
+ IdPool::CreateInstance();
+ IdPool* instance = IdPool::Instance();
+
+ std::set<uint16> in_use;
+ for (int run = 0; run < 0xFFFE; ++run) {
+ uint16 new_id = instance->Alloc();
+ EXPECT_NE(0, new_id);
+ EXPECT_NE(IdPool::kOverFlowId, new_id);
+ EXPECT_TRUE(in_use.find(new_id) == in_use.end());
+ in_use.insert(new_id);
+ }
+
+ // All attempts after this point should return kOverFlowId.
+ for (int run = 0; run < 100; ++run) {
+ EXPECT_EQ(IdPool::kOverFlowId, instance->Alloc());
+ }
+
+ // Trying to free the overflow ID is harmless.
+ instance->Free(IdPool::kOverFlowId);
+
+ // Now delete half of them.
+ int deleted = 0;
+ std::set<uint16>::iterator i = in_use.begin();
+ while (deleted != 0xFFFE / 2) {
+ ASSERT_TRUE(i != in_use.end());
+ instance->Free(*i);
+ ++deleted;
+ in_use.erase(i);
+ i = in_use.begin();
+ }
+
+ // Should now be able to allocate that many again.
+ for (int run = 0; run < 0xFFFE / 2; ++run) {
+ uint16 new_id = instance->Alloc();
+ EXPECT_NE(0, new_id);
+ EXPECT_NE(IdPool::kOverFlowId, new_id);
+ EXPECT_TRUE(in_use.find(new_id) == in_use.end());
+ in_use.insert(new_id);
+ }
+
+ IdPool::DestroyInstance();
+}
+
+} // namespace
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.cc Thu May 1 11:39:27 2014
@@ -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 <limits>
+#include <string>
+
+#include "httpd.h"
+// When HAVE_SYSLOG is defined, apache http_log.h will include syslog.h, which
+// #defined LOG_* as numbers. This conflicts with what we are using those here.
+#undef HAVE_SYSLOG
+#include "http_log.h"
+
+#include "base/debug/debugger.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/threading/thread_local.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/spdy_stream.h"
+#include "mod_spdy/common/version.h"
+
+// Make sure we don't attempt to use LOG macros here, since doing so
+// would cause us to go into an infinite log loop.
+#undef LOG
+#define LOG USING_LOG_HERE_WOULD_CAUSE_INFINITE_RECURSION
+
+namespace {
+
+class LogHandler;
+
+const char* const kLogMessagePrefix =
+ "[mod_spdy/" MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING "] ";
+
+apr_pool_t* log_pool = NULL;
+base::ThreadLocalPointer<LogHandler>* gThreadLocalLogHandler = NULL;
+
+const int kMaxInt = std::numeric_limits<int>::max();
+int log_level_cutoff = kMaxInt;
+
+class LogHandler {
+ public:
+ explicit LogHandler(LogHandler* parent) : parent_(parent) {}
+ virtual ~LogHandler() {}
+ virtual void Log(int log_level, const std::string& message) = 0;
+ LogHandler* parent() const { return parent_; }
+ private:
+ LogHandler* parent_;
+ DISALLOW_COPY_AND_ASSIGN(LogHandler);
+};
+
+// Log a message with the given LogHandler; if the LogHandler is NULL, fall
+// back to using ap_log_perror.
+void LogWithHandler(LogHandler* handler, int log_level,
+ const std::string& message) {
+ if (handler != NULL) {
+ handler->Log(log_level, message);
+ } else {
+ // ap_log_perror only prints messages with a severity of at least NOTICE,
+ // so if we're falling back to ap_log_perror (which should be rare) then
+ // force the log_level to a verbosity of NOTICE or lower.
+ COMPILE_ASSERT(APLOG_DEBUG > APLOG_NOTICE,
+ higher_verbosity_is_higher_number);
+ ap_log_perror(APLOG_MARK, std::min(log_level, APLOG_NOTICE), APR_SUCCESS,
+ log_pool, "%s", message.c_str());
+ }
+}
+
+void PopLogHandler() {
+ CHECK(gThreadLocalLogHandler);
+ LogHandler* handler = gThreadLocalLogHandler->Get();
+ CHECK(handler);
+ gThreadLocalLogHandler->Set(handler->parent());
+ delete handler;
+}
+
+class ServerLogHandler : public LogHandler {
+ public:
+ ServerLogHandler(LogHandler* parent, server_rec* server)
+ : LogHandler(parent), server_(server) {}
+ virtual void Log(int log_level, const std::string& message) {
+ ap_log_error(APLOG_MARK, log_level, APR_SUCCESS, server_,
+ "%s", message.c_str());
+ }
+ private:
+ server_rec* const server_;
+ DISALLOW_COPY_AND_ASSIGN(ServerLogHandler);
+};
+
+class ConnectionLogHandler : public LogHandler {
+ public:
+ ConnectionLogHandler(LogHandler* parent, conn_rec* connection)
+ : LogHandler(parent), connection_(connection) {}
+ virtual void Log(int log_level, const std::string& message) {
+ ap_log_cerror(APLOG_MARK, log_level, APR_SUCCESS, connection_,
+ "%s", message.c_str());
+ }
+ private:
+ conn_rec* const connection_;
+ DISALLOW_COPY_AND_ASSIGN(ConnectionLogHandler);
+};
+
+class StreamLogHandler : public LogHandler {
+ public:
+ StreamLogHandler(LogHandler* parent, conn_rec* connection,
+ const mod_spdy::SpdyStream* stream)
+ : LogHandler(parent), connection_(connection), stream_(stream) {}
+ virtual void Log(int log_level, const std::string& message) {
+ ap_log_cerror(APLOG_MARK, log_level, APR_SUCCESS, connection_,
+ "[stream %d] %s", static_cast<int>(stream_->stream_id()),
+ message.c_str());
+ }
+ private:
+ conn_rec* const connection_;
+ const mod_spdy::SpdyStream* const stream_;
+ DISALLOW_COPY_AND_ASSIGN(StreamLogHandler);
+};
+
+int GetApacheLogLevel(int severity) {
+ switch (severity) {
+ case logging::LOG_INFO:
+ return APLOG_INFO;
+ case logging::LOG_WARNING:
+ return APLOG_WARNING;
+ case logging::LOG_ERROR:
+ return APLOG_ERR;
+ case logging::LOG_ERROR_REPORT:
+ return APLOG_CRIT;
+ case logging::LOG_FATAL:
+ return APLOG_ALERT;
+ default: // For VLOG()s
+ return APLOG_DEBUG;
+ }
+}
+
+bool LogMessageHandler(int severity, const char* file, int line,
+ size_t message_start, const std::string& str) {
+ const int this_log_level = GetApacheLogLevel(severity);
+
+ std::string message(kLogMessagePrefix);
+ message.append(str);
+ if (severity == logging::LOG_FATAL) {
+ if (base::debug::BeingDebugged()) {
+ base::debug::BreakDebugger();
+ } else {
+ base::debug::StackTrace trace;
+ std::ostringstream stream;
+ trace.OutputToStream(&stream);
+ message.append(stream.str());
+ }
+ }
+
+ // Trim the newline off the end of the message string.
+ size_t last_msg_character_index = message.length() - 1;
+ if (message[last_msg_character_index] == '\n') {
+ message.resize(last_msg_character_index);
+ }
+
+ if (this_log_level <= log_level_cutoff || log_level_cutoff == kMaxInt) {
+ LogWithHandler(gThreadLocalLogHandler->Get(), this_log_level, message);
+ }
+
+ if (severity == logging::LOG_FATAL) {
+ // Crash the process to generate a dump.
+ base::debug::BreakDebugger();
+ }
+
+ return true;
+}
+
+// Include PID and TID in each log message.
+bool kShowProcessId = true;
+bool kShowThreadId = true;
+
+// Disabled since this information is already included in the apache
+// log line.
+bool kShowTimestamp = false;
+
+// Disabled by default due to CPU cost. Enable to see high-resolution
+// timestamps in the logs.
+bool kShowTickcount = false;
+
+} // namespace
+
+namespace mod_spdy {
+
+ScopedServerLogHandler::ScopedServerLogHandler(server_rec* server) {
+ CHECK(gThreadLocalLogHandler);
+ gThreadLocalLogHandler->Set(new ServerLogHandler(
+ gThreadLocalLogHandler->Get(), server));
+}
+
+ScopedServerLogHandler::~ScopedServerLogHandler() {
+ PopLogHandler();
+}
+
+ScopedConnectionLogHandler::ScopedConnectionLogHandler(conn_rec* connection) {
+ CHECK(gThreadLocalLogHandler);
+ gThreadLocalLogHandler->Set(new ConnectionLogHandler(
+ gThreadLocalLogHandler->Get(), connection));
+}
+
+ScopedConnectionLogHandler::~ScopedConnectionLogHandler() {
+ PopLogHandler();
+}
+
+ScopedStreamLogHandler::ScopedStreamLogHandler(conn_rec* slave_connection,
+ const SpdyStream* stream) {
+ CHECK(gThreadLocalLogHandler);
+ gThreadLocalLogHandler->Set(new StreamLogHandler(
+ gThreadLocalLogHandler->Get(), slave_connection, stream));
+}
+
+ScopedStreamLogHandler::~ScopedStreamLogHandler() {
+ PopLogHandler();
+}
+
+void InstallLogMessageHandler(apr_pool_t* pool) {
+ log_pool = pool;
+ gThreadLocalLogHandler = new base::ThreadLocalPointer<LogHandler>();
+ PoolRegisterDelete(pool, gThreadLocalLogHandler);
+ logging::SetLogItems(kShowProcessId,
+ kShowThreadId,
+ kShowTimestamp,
+ kShowTickcount);
+ logging::SetLogMessageHandler(&LogMessageHandler);
+}
+
+void SetLoggingLevel(int apache_log_level, int vlog_level) {
+ switch (apache_log_level) {
+ case APLOG_EMERG:
+ case APLOG_ALERT:
+ logging::SetMinLogLevel(logging::LOG_FATAL);
+ break;
+ case APLOG_CRIT:
+ logging::SetMinLogLevel(logging::LOG_ERROR_REPORT);
+ break;
+ case APLOG_ERR:
+ logging::SetMinLogLevel(logging::LOG_ERROR);
+ break;
+ case APLOG_WARNING:
+ logging::SetMinLogLevel(logging::LOG_WARNING);
+ break;
+ case APLOG_NOTICE:
+ case APLOG_INFO:
+ case APLOG_DEBUG:
+ default:
+ logging::SetMinLogLevel(std::min(logging::LOG_INFO, -vlog_level));
+ break;
+ }
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.h Thu May 1 11:39:27 2014
@@ -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 <string>
+
+#include "httpd.h"
+#include "apr_pools.h"
+
+#include "base/basictypes.h"
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// Stack-allocate to install a server-specific log handler for the duration of
+// the current scope (on the current thread only). For example:
+//
+// void SomeApacheHookFunction(server_rec* server) {
+// ScopedServerLogHandler handler(server);
+// ...call various other functions here...
+// }
+//
+// The log handler will be in effect until the end of the block, but only for
+// this thread (even if this thread spawns other threads in the meantime).
+// Establishing this server-specific log handler allows LOG() macros within
+// called functions to produce better messages.
+class ScopedServerLogHandler {
+ public:
+ explicit ScopedServerLogHandler(server_rec* server);
+ ~ScopedServerLogHandler();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedServerLogHandler);
+};
+
+// Stack-allocate to install a connection-specific log handler for the duration
+// of the current scope (on the current thread only). See the doc comment for
+// ScopedServerLogHandler above for an example.
+class ScopedConnectionLogHandler {
+ public:
+ explicit ScopedConnectionLogHandler(conn_rec* connection);
+ ~ScopedConnectionLogHandler();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedConnectionLogHandler);
+};
+
+// Stack-allocate to install a stream-specific log handler for the duration of
+// the current scope (on the current thread only). See the doc comment for
+// ScopedServerLogHandler above for an example.
+class ScopedStreamLogHandler {
+ public:
+ explicit ScopedStreamLogHandler(conn_rec* slave_connection,
+ const SpdyStream* stream);
+ ~ScopedStreamLogHandler();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedStreamLogHandler);
+};
+
+// Install a log message handler that routes LOG() messages to the
+// apache error log. Should be called once, at server startup.
+void InstallLogMessageHandler(apr_pool_t* pool);
+
+// Set the logging level for LOG() messages, based on the Apache log level and
+// the VLOG-level specified in the server config. Note that the VLOG level
+// will be ignored unless the Apache log verbosity is at NOTICE or higher.
+// Should be called once for each child process, at process startup.
+void SetLoggingLevel(int apache_log_level, int vlog_level);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_LOG_MESSAGE_HANDLER_H_
Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/log_message_handler.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.cc Thu May 1 11:39:27 2014
@@ -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
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.h Thu May 1 11:39:27 2014
@@ -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 <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// Shared context object for a SPDY connection to the outside world.
+class MasterConnectionContext {
+ public:
+ // Create a context object for a master connection (one to the outside world,
+ // not for talking to Apache).
+ explicit MasterConnectionContext(bool using_ssl);
+ ~MasterConnectionContext();
+
+ // Return true if the connection to the user is over SSL. This is almost
+ // always true, but may be false if we've been set to use SPDY for non-SSL
+ // connections (for debugging).
+ bool is_using_ssl() const { return using_ssl_; }
+
+ // Return true if we are using SPDY for this connection, which is the case if
+ // either 1) SPDY was chosen by NPN, or 2) we are assuming SPDY regardless of
+ // NPN.
+ bool is_using_spdy() const;
+
+ enum NpnState {
+ // NOT_DONE_YET: NPN has not yet completed.
+ NOT_DONE_YET,
+ // USING_SPDY: We have agreed with the client to use SPDY for this
+ // connection.
+ USING_SPDY,
+ // NOT_USING_SPDY: We have decided not to use SPDY for this connection.
+ NOT_USING_SPDY
+ };
+
+ // Get the NPN state of this connection. Unless you actually care about NPN
+ // itself, you probably don't want to use this method to check if SPDY is
+ // being used; instead, use is_using_spdy().
+ NpnState npn_state() const;
+
+ // Set the NPN state of this connection.
+ void set_npn_state(NpnState state);
+
+ // If true, we are simply _assuming_ SPDY, regardless of the outcome of NPN.
+ bool is_assuming_spdy() const;
+
+ // Set whether we are assuming SPDY for this connection (regardless of NPN).
+ void set_assume_spdy(bool assume);
+
+ // Return the SPDY version number we will be using. Requires that
+ // is_using_spdy() is true and that the version number has already been set.
+ spdy::SpdyVersion spdy_version() const;
+
+ // Set the SPDY version number we will be using. Requires that
+ // is_using_spdy() is true, and set_spdy_version hasn't already been called.
+ void set_spdy_version(spdy::SpdyVersion spdy_version);
+
+ private:
+ const bool using_ssl_;
+ NpnState npn_state_;
+ bool assume_spdy_;
+ spdy::SpdyVersion spdy_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(MasterConnectionContext);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_MASTER_CONNECTION_CONTEXT_H_
Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/master_connection_context.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.cc Thu May 1 11:39:27 2014
@@ -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 <string>
+
+#include "apr_errno.h"
+
+#include "base/basictypes.h"
+
+namespace mod_spdy {
+
+std::string AprStatusString(apr_status_t status) {
+ char buffer[120];
+ apr_strerror(status, buffer, arraysize(buffer));
+ return std::string(buffer);
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.h Thu May 1 11:39:27 2014
@@ -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 <string>
+
+#include "apr_pools.h"
+#include "base/logging.h"
+
+namespace mod_spdy {
+
+/**
+ * Wrapper object that creates a new apr_pool_t and then destroys it when
+ * deleted (handy for creating a local apr_pool_t on the stack).
+ *
+ * Example usage:
+ *
+ * apr_status_t SomeFunction() {
+ * LocalPool local;
+ * char* buffer = apr_palloc(local.pool(), 1024);
+ * // Do stuff with buffer; it will dealloc when we leave this scope.
+ * return APR_SUCCESS;
+ * }
+ */
+class LocalPool {
+ public:
+ LocalPool() : pool_(NULL) {
+ // apr_pool_create() only fails if we run out of memory. However, we make
+ // no effort elsewhere in this codebase to deal with running out of memory,
+ // so there's no sense in dealing with it here. Instead, just assert that
+ // pool creation succeeds.
+ const apr_status_t status = apr_pool_create(&pool_, NULL);
+ CHECK(status == APR_SUCCESS);
+ CHECK(pool_ != NULL);
+ }
+
+ ~LocalPool() {
+ apr_pool_destroy(pool_);
+ }
+
+ apr_pool_t* pool() const { return pool_; }
+
+ private:
+ apr_pool_t* pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalPool);
+};
+
+// Helper function for PoolRegisterDelete.
+template <class T>
+apr_status_t DeletionFunction(void* object) {
+ delete static_cast<T*>(object);
+ return APR_SUCCESS;
+}
+
+// Register a C++ object to be deleted with a pool.
+template <class T>
+void PoolRegisterDelete(apr_pool_t* pool, T* object) {
+ // Note that the "child cleanup" argument below doesn't apply to us, so we
+ // use apr_pool_cleanup_null, which is a no-op cleanup function.
+ apr_pool_cleanup_register(pool, object,
+ DeletionFunction<T>, // cleanup function
+ apr_pool_cleanup_null); // child cleanup
+}
+
+// Un-register a C++ object from deletion with a pool. Essentially, this
+// undoes a previous call to PoolRegisterDelete with the same pool and object.
+template <class T>
+void PoolUnregisterDelete(apr_pool_t* pool, T* object) {
+ apr_pool_cleanup_kill(pool, object, DeletionFunction<T>);
+}
+
+// Return a string describing the given APR status code.
+std::string AprStatusString(apr_status_t status);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_APACHE_POOL_UTIL_H_
Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/apache/pool_util_test.cc Thu May 1 11:39:27 2014
@@ -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