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:43:45 UTC

svn commit: r1591622 [8/33] - in /httpd/mod_spdy/trunk: ./ base/ base/base.xcodeproj/ base/metrics/ build/ build/all.xcodeproj/ build/build_util.xcodeproj/ build/install.xcodeproj/ build/internal/ build/linux/ build/mac/ build/util/ build/win/ install/...

Added: httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,349 @@
+// 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/string_piece.h"
+#include "base/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;
+}
+
+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::SpdyFrame> frame;
+  {
+    net::SpdyFrame* 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.
+  if (frame->is_control_frame()) {
+    net::SpdyControlFrame* ctrl_frame =
+        static_cast<net::SpdyControlFrame*>(frame.get());
+    switch (ctrl_frame->type()) {
+      case net::SYN_STREAM:
+        return DecodeSynStreamFrame(
+            *static_cast<net::SpdySynStreamControlFrame*>(ctrl_frame));
+      case net::HEADERS:
+        return DecodeHeadersFrame(
+            *static_cast<net::SpdyHeadersControlFrame*>(ctrl_frame));
+      default:
+        // Other frame types should be handled by the master connection, rather
+        // than sent here.
+        LOG(DFATAL) << "Master connection sent a frame of type "
+                    << ctrl_frame->type() << " to stream "
+                    << stream_->stream_id();
+        AbortStream(net::INTERNAL_ERROR);
+        return false;
+    }
+  } else {
+    return DecodeDataFrame(
+        *static_cast<net::SpdyDataFrame*>(frame.get()));
+  }
+}
+
+bool SpdyToHttpFilter::DecodeSynStreamFrame(
+    const net::SpdySynStreamControlFrame& 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::PROTOCOL_ERROR);
+      return false;
+    case SpdyToHttpConverter::INVALID_HEADER_BLOCK:
+      LOG(ERROR) << "Invalid SYN_STREAM header block on stream "
+                 << stream_->stream_id();
+      AbortStream(net::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::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::INTERNAL_ERROR);
+      return false;
+  }
+}
+
+bool SpdyToHttpFilter::DecodeHeadersFrame(
+    const net::SpdyHeadersControlFrame& frame) {
+  const SpdyToHttpConverter::Status status =
+      converter_.ConvertHeadersFrame(frame);
+  switch (status) {
+    case SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS:
+      return true;
+    case SpdyToHttpConverter::FRAME_AFTER_FIN:
+      AbortStream(net::INVALID_STREAM);
+      return false;
+    case SpdyToHttpConverter::INVALID_HEADER_BLOCK:
+      LOG(ERROR) << "Invalid HEADERS header block on stream "
+                 << stream_->stream_id();
+      AbortStream(net::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::INTERNAL_ERROR);
+      return false;
+  }
+}
+
+bool SpdyToHttpFilter::DecodeDataFrame(const net::SpdyDataFrame& 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.length());
+      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::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::INTERNAL_ERROR);
+      return false;
+  }
+}
+
+void SpdyToHttpFilter::AbortStream(net::SpdyStatusCodes status) {
+  stream_->AbortWithRstStream(status);
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter.h Thu May  1 11:43:36 2014
@@ -0,0 +1,79 @@
+// 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_framer.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:
+  // 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::SpdySynStreamControlFrame& frame);
+  bool DecodeHeadersFrame(const net::SpdyHeadersControlFrame& frame);
+  bool DecodeDataFrame(const net::SpdyDataFrame& frame);
+
+  // Send a RST_STREAM frame and abort the stream.
+  void AbortStream(net::SpdyStatusCodes 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/trunk/mod_spdy/apache/filters/spdy_to_http_filter.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/filters/spdy_to_http_filter_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,668 @@
+// 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/string_piece.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/common/protocol_util.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/buffered_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::IsRstStream;
+using mod_spdy::testing::StreamIdIs;
+using testing::AllOf;
+
+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 SpdyToHttpFilterTest :
+      public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+  SpdyToHttpFilterTest()
+      : spdy_version_(GetParam()),
+        framer_(mod_spdy::SpdyVersionToFramerVersion(spdy_version_)),
+        stream_id_(1),
+        priority_(framer_.GetHighestPriority()),
+        stream_(spdy_version_, stream_id_, 0, 0, priority_,
+                net::kSpdyStreamInitialWindowSize, &output_queue_, &framer_,
+                &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(net::SpdyControlFlags flags,
+                          net::SpdyHeaderBlock* headers) {
+    stream_.PostInputFrame(framer_.CreateSynStream(
+        stream_id_,
+        0,  // associated_stream_id
+        priority_,
+        0,  // credential slot
+        flags,
+        false, // compressed
+        headers));
+  }
+
+  void PostHeadersFrame(net::SpdyControlFlags flags,
+                        net::SpdyHeaderBlock* headers) {
+    stream_.PostInputFrame(framer_.CreateHeaders(
+        stream_id_,
+        flags,
+        false, // compressed
+        headers));
+  }
+
+  void PostDataFrame(net::SpdyDataFlags flags,
+                     const base::StringPiece& payload) {
+    stream_.PostInputFrame(framer_.CreateDataFrame(
+        stream_id_, payload.data(), payload.size(), flags));
+  }
+
+  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::SpdyStatusCodes status) {
+    net::SpdyFrame* raw_frame;
+    ASSERT_TRUE(output_queue_.Pop(&raw_frame))
+        << "Expected RST_STREAM frame, but output queue is empty.";
+    scoped_ptr<net::SpdyFrame> frame(raw_frame);
+    EXPECT_THAT(*frame, AllOf(IsRstStream(status), StreamIdIs(stream_id_)));
+  }
+
+  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_;
+  net::BufferedSpdyFramer framer_;
+  const net::SpdyStreamId stream_id_;
+  const net::SpdyPriority priority_;
+  mod_spdy::SpdyFramePriorityQueue output_queue_;
+  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::SpdyHeaderBlock 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(net::CONTROL_FLAG_FIN, &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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &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(net::DATA_FLAG_NONE, "Hello, world!\nPlease erase ");
+  PostDataFrame(net::DATA_FLAG_NONE, "the whole database ");
+  PostDataFrame(net::DATA_FLAG_FIN, "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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &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(net::DATA_FLAG_NONE, "Please erase ");
+  net::SpdyHeaderBlock headers2;
+  headers2["x-super-cool"] = "foo";
+  PostHeadersFrame(net::CONTROL_FLAG_NONE, &headers2);
+  PostDataFrame(net::DATA_FLAG_NONE, "everything ");
+  net::SpdyHeaderBlock headers3;
+  headers3["x-awesome"] = "quux";
+  PostHeadersFrame(net::CONTROL_FLAG_NONE, &headers3);
+  PostDataFrame(net::DATA_FLAG_FIN, "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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &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::SpdyHeaderBlock headers2;
+  headers2["accept-encoding"] = "deflate, gzip";
+  headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+  PostHeadersFrame(net::CONTROL_FLAG_FIN, &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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &headers);
+
+  // Send a HEADERS frame before sending any data frames.
+  net::SpdyHeaderBlock headers2;
+  headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+  PostHeadersFrame(net::CONTROL_FLAG_NONE, &headers2);
+
+  // Now send a couple DATA frames and a final HEADERS frame.
+  PostDataFrame(net::DATA_FLAG_NONE, "Please erase everything immediately");
+  PostDataFrame(net::DATA_FLAG_NONE, ", thanks!\n");
+  net::SpdyHeaderBlock headers3;
+  headers3["x-qqq"] = "3Q";
+  PostHeadersFrame(net::CONTROL_FLAG_FIN, &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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &headers);
+
+  // Now send a few DATA frames, with a zero-length data frame in the middle.
+  PostDataFrame(net::DATA_FLAG_NONE, "Please do");
+  PostDataFrame(net::DATA_FLAG_NONE, " some ");
+  PostDataFrame(net::DATA_FLAG_NONE, "");
+  PostDataFrame(net::DATA_FLAG_FIN, "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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &headers);
+
+  // Now send a few DATA frames, with a zero-length data frame at the end.
+  PostDataFrame(net::DATA_FLAG_NONE, "Please do");
+  PostDataFrame(net::DATA_FLAG_NONE, " some ");
+  PostDataFrame(net::DATA_FLAG_NONE, "stuff.\n");
+  PostDataFrame(net::DATA_FLAG_FIN, "");
+
+  // 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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &headers);
+
+  // Send a few more headers before sending data, including a content-length.
+  net::SpdyHeaderBlock headers2;
+  headers2["content-length"] = "22";
+  headers2["user-agent"] = "ModSpdyUnitTest/1.0";
+  PostHeadersFrame(net::CONTROL_FLAG_NONE, &headers2);
+
+  // Now send a few DATA frames.
+  PostDataFrame(net::DATA_FLAG_NONE, "Please do");
+  PostDataFrame(net::DATA_FLAG_NONE, " some ");
+  PostDataFrame(net::DATA_FLAG_FIN, "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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &headers);
+
+  // Now send a few DATA frames.
+  PostDataFrame(net::DATA_FLAG_NONE, "Please do");
+  PostDataFrame(net::DATA_FLAG_NONE, " some ");
+  PostDataFrame(net::DATA_FLAG_NONE, "stuff.\n");
+
+  // Finish with a HEADERS frame.
+  net::SpdyHeaderBlock headers2;
+  headers2["x-metadata"] = "foobar";
+  headers2["x-whatever"] = "quux";
+  PostHeadersFrame(net::CONTROL_FLAG_FIN, &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::SpdyHeaderBlock 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(net::CONTROL_FLAG_NONE, &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(net::CONTROL_FLAG_NONE, &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::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/trunk/mod_spdy/apache/id_pool.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/id_pool.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/id_pool.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/id_pool.cc Thu May  1 11:43:36 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/trunk/mod_spdy/apache/id_pool.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/id_pool.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/id_pool.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/id_pool.h Thu May  1 11:43:36 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/trunk/mod_spdy/apache/id_pool.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/apache/id_pool_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/id_pool_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/id_pool_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/id_pool_test.cc Thu May  1 11:43:36 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/trunk/mod_spdy/apache/log_message_handler.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/log_message_handler.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/log_message_handler.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/log_message_handler.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,265 @@
+// 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"
+APLOG_USE_MODULE(spdy);
+
+#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/trunk/mod_spdy/apache/log_message_handler.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/log_message_handler.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/log_message_handler.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/log_message_handler.h Thu May  1 11:43:36 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/trunk/mod_spdy/apache/log_message_handler.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.cc Thu May  1 11:43:36 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/trunk/mod_spdy/apache/master_connection_context.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/master_connection_context.h Thu May  1 11:43:36 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/trunk/mod_spdy/apache/master_connection_context.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.cc Thu May  1 11:43:36 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/trunk/mod_spdy/apache/pool_util.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/pool_util.h Thu May  1 11:43:36 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/trunk/mod_spdy/apache/pool_util.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/apache/pool_util_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/apache/pool_util_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/apache/pool_util_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/apache/pool_util_test.cc Thu May  1 11:43:36 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