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 [10/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/common/http_string_builder.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,122 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/http_string_builder.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/string_piece.h"
+#include "base/stringprintf.h"
+
+namespace {
+
+void OnHeader(const base::StringPiece& key,
+              const base::StringPiece& value,
+              std::string* output) {
+  key.AppendToString(output);
+  output->append(": ");
+  value.AppendToString(output);
+  output->append("\r\n");
+}
+
+}  // namespace
+
+namespace mod_spdy {
+
+HttpStringBuilder::HttpStringBuilder(std::string* str)
+    : string_(str), state_(REQUEST_LINE) {
+  CHECK(string_);
+}
+
+HttpStringBuilder::~HttpStringBuilder() {}
+
+void HttpStringBuilder::OnRequestLine(const base::StringPiece& method,
+                                      const base::StringPiece& path,
+                                      const base::StringPiece& version) {
+  DCHECK(state_ == REQUEST_LINE);
+  state_ = LEADING_HEADERS;
+  method.AppendToString(string_);
+  string_->push_back(' ');
+  path.AppendToString(string_);
+  string_->push_back(' ');
+  version.AppendToString(string_);
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnLeadingHeader(const base::StringPiece& key,
+                                        const base::StringPiece& value) {
+  DCHECK(state_ == LEADING_HEADERS);
+  OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnLeadingHeadersComplete() {
+  DCHECK(state_ == LEADING_HEADERS);
+  state_ = LEADING_HEADERS_COMPLETE;
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnRawData(const base::StringPiece& data) {
+  DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == RAW_DATA);
+  state_ = RAW_DATA;
+  data.AppendToString(string_);
+}
+
+void HttpStringBuilder::OnDataChunk(const base::StringPiece& data) {
+  DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == DATA_CHUNKS);
+  state_ = DATA_CHUNKS;
+  // Encode the data as an HTTP data chunk.  See RFC 2616 section 3.6.1 for
+  // details.
+  base::StringAppendF(string_, "%lX\r\n",
+                      static_cast<unsigned long>(data.size()));
+  data.AppendToString(string_);
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnDataChunksComplete() {
+  DCHECK(state_ == DATA_CHUNKS);
+  state_ = DATA_CHUNKS_COMPLETE;
+  // Indicate that there are no more HTTP data chunks coming.  See RFC 2616
+  // section 3.6.1 for details.
+  string_->append("0\r\n");
+}
+
+void HttpStringBuilder::OnTrailingHeader(const base::StringPiece& key,
+                                         const base::StringPiece& value) {
+  DCHECK(state_ == DATA_CHUNKS_COMPLETE || state_ == TRAILING_HEADERS);
+  state_ = TRAILING_HEADERS;
+  OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnTrailingHeadersComplete() {
+  DCHECK(state_ == TRAILING_HEADERS);
+  state_ = TRAILING_HEADERS_COMPLETE;
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnComplete() {
+  DCHECK(state_ == LEADING_HEADERS_COMPLETE ||
+         state_ == RAW_DATA ||
+         state_ == DATA_CHUNKS_COMPLETE ||
+         state_ == TRAILING_HEADERS_COMPLETE);
+  if (state_ == DATA_CHUNKS_COMPLETE) {
+    // In this case, there have been data chunks, but we haven't called
+    // OnTrailingHeadersComplete because there were no trailing headers.  We
+    // still need an empty line to indicate the end of the request.
+    string_->append("\r\n");
+  }
+  state_ = COMPLETE;
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h Thu May  1 11:43:36 2014
@@ -0,0 +1,69 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
+#define MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+
+namespace mod_spdy {
+
+// An HttpRequestVisitorInterface class that appends to a std::string.
+class HttpStringBuilder : public HttpRequestVisitorInterface {
+ public:
+  explicit HttpStringBuilder(std::string* str);
+  virtual ~HttpStringBuilder();
+
+  bool is_complete() const { return state_ == COMPLETE; }
+
+  // HttpRequestVisitorInterface methods:
+  virtual void OnRequestLine(const base::StringPiece& method,
+                             const base::StringPiece& path,
+                             const base::StringPiece& version);
+  virtual void OnLeadingHeader(const base::StringPiece& key,
+                            const base::StringPiece& value);
+  virtual void OnLeadingHeadersComplete();
+  virtual void OnRawData(const base::StringPiece& data);
+  virtual void OnDataChunk(const base::StringPiece& data);
+  virtual void OnDataChunksComplete();
+  virtual void OnTrailingHeader(const base::StringPiece& key,
+                                const base::StringPiece& value);
+  virtual void OnTrailingHeadersComplete();
+  virtual void OnComplete();
+
+ private:
+  enum State {
+    REQUEST_LINE,
+    LEADING_HEADERS,
+    LEADING_HEADERS_COMPLETE,
+    RAW_DATA,
+    DATA_CHUNKS,
+    DATA_CHUNKS_COMPLETE,
+    TRAILING_HEADERS,
+    TRAILING_HEADERS_COMPLETE,
+    COMPLETE
+  };
+
+  std::string* const string_;
+  State state_;
+
+  DISALLOW_COPY_AND_ASSIGN(HttpStringBuilder);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,192 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_piece.h"
+#include "mod_spdy/common/http_response_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// This is the number of bytes we want to send per data frame.  We never send
+// data frames larger than this, but we might send smaller ones if we have to
+// flush early.
+// TODO The SPDY folks say that smallish (~4kB) data frames are good; however,
+//      we should experiment later on to see what value here performs the best.
+const size_t kTargetDataFrameBytes = 4096;
+
+}  // namespace
+
+namespace mod_spdy {
+
+class HttpToSpdyConverter::ConverterImpl : public HttpResponseVisitorInterface{
+ public:
+  ConverterImpl(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+  virtual ~ConverterImpl();
+
+  void Flush();
+
+  // HttpResponseVisitorInterface methods:
+  virtual void OnStatusLine(const base::StringPiece& version,
+                            const base::StringPiece& status_code,
+                            const base::StringPiece& status_phrase);
+  virtual void OnLeadingHeader(const base::StringPiece& key,
+                               const base::StringPiece& value);
+  virtual void OnLeadingHeadersComplete(bool fin);
+  virtual void OnData(const base::StringPiece& data, bool fin);
+
+ private:
+  void SendDataIfNecessary(bool flush, bool fin);
+  void SendDataFrame(const char* data, size_t size, bool flag_fin);
+
+  const spdy::SpdyVersion spdy_version_;
+  SpdyReceiver* const receiver_;
+  net::SpdyHeaderBlock headers_;
+  std::string data_buffer_;
+  bool sent_flag_fin_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConverterImpl);
+};
+
+HttpToSpdyConverter::SpdyReceiver::SpdyReceiver() {}
+
+HttpToSpdyConverter::SpdyReceiver::~SpdyReceiver() {}
+
+HttpToSpdyConverter::HttpToSpdyConverter(spdy::SpdyVersion spdy_version,
+                                         SpdyReceiver* receiver)
+    : impl_(new ConverterImpl(spdy_version, receiver)),
+      parser_(impl_.get()) {}
+
+HttpToSpdyConverter::~HttpToSpdyConverter() {}
+
+bool HttpToSpdyConverter::ProcessInput(base::StringPiece input_data) {
+  return parser_.ProcessInput(input_data);
+}
+
+void HttpToSpdyConverter::Flush() {
+  impl_->Flush();
+}
+
+HttpToSpdyConverter::ConverterImpl::ConverterImpl(
+    spdy::SpdyVersion spdy_version, SpdyReceiver* receiver)
+    : spdy_version_(spdy_version),
+      receiver_(receiver),
+      sent_flag_fin_(false) {
+  DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+  CHECK(receiver_);
+}
+
+HttpToSpdyConverter::ConverterImpl::~ConverterImpl() {}
+
+void HttpToSpdyConverter::ConverterImpl::Flush() {
+  SendDataIfNecessary(true,  // true = do flush
+                      false);  // false = not fin yet
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnStatusLine(
+    const base::StringPiece& version,
+    const base::StringPiece& status_code,
+    const base::StringPiece& status_phrase) {
+  DCHECK(headers_.empty());
+  const bool spdy2 = spdy_version_ < spdy::SPDY_VERSION_3;
+  headers_[spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version] =
+      version.as_string();
+  headers_[spdy2 ? spdy::kSpdy2Status : spdy::kSpdy3Status] =
+      status_code.as_string();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeader(
+    const base::StringPiece& key,
+    const base::StringPiece& value) {
+  // Filter out headers that are invalid in SPDY.
+  if (IsInvalidSpdyResponseHeader(key)) {
+    return;
+  }
+  MergeInHeader(key, value, &headers_);
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeadersComplete(bool fin) {
+  if (sent_flag_fin_) {
+    LOG(DFATAL) << "Trying to send headers after sending FLAG_FIN";
+    return;
+  }
+  if (fin) {
+    sent_flag_fin_ = true;
+  }
+  receiver_->ReceiveSynReply(&headers_, fin);
+  headers_.clear();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnData(const base::StringPiece& data,
+                                                bool fin) {
+  data.AppendToString(&data_buffer_);
+  SendDataIfNecessary(false, fin);  // false = don't flush
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataIfNecessary(bool flush,
+                                                             bool fin) {
+  // If we have (strictly) more than one frame's worth of data waiting, send it
+  // down the filter chain, kTargetDataFrameBytes bytes at a time.  If we are
+  // left with _exactly_ kTargetDataFrameBytes bytes of data, we'll deal with
+  // that in the next code block (see the comment there to explain why).
+  if (data_buffer_.size() > kTargetDataFrameBytes) {
+    const char* start = data_buffer_.data();
+    size_t size = data_buffer_.size();
+    while (size > kTargetDataFrameBytes) {
+      SendDataFrame(start, kTargetDataFrameBytes, false);
+      start += kTargetDataFrameBytes;
+      size -= kTargetDataFrameBytes;
+    }
+    data_buffer_.erase(0, data_buffer_.size() - size);
+  }
+  DCHECK(data_buffer_.size() <= kTargetDataFrameBytes);
+
+  // We may still have some leftover data.  We need to send another data frame
+  // now (rather than waiting for a full kTargetDataFrameBytes) if:
+  //   1) This is the end of the response,
+  //   2) we're supposed to flush and the buffer is nonempty, or
+  //   3) we still have a full data frame's worth in the buffer.
+  //
+  // Note that because of the previous code block, condition (3) will only be
+  // true if we have exactly kTargetDataFrameBytes of data.  However, dealing
+  // with that case here instead of in the above block makes it easier to make
+  // sure we correctly set FLAG_FIN on the final data frame, which is why the
+  // above block uses a strict, > comparison rather than a non-strict, >=
+  // comparison.
+  if (fin || (flush && !data_buffer_.empty()) ||
+      data_buffer_.size() >= kTargetDataFrameBytes) {
+    SendDataFrame(data_buffer_.data(), data_buffer_.size(), fin);
+    data_buffer_.clear();
+  }
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataFrame(
+    const char* data, size_t size, bool flag_fin) {
+  if (sent_flag_fin_) {
+    LOG(DFATAL) << "Trying to send data after sending FLAG_FIN";
+    return;
+  }
+  if (flag_fin) {
+    sent_flag_fin_ = true;
+  }
+  receiver_->ReceiveData(base::StringPiece(data, size), flag_fin);
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h Thu May  1 11:43:36 2014
@@ -0,0 +1,78 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
+#define MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_piece.h"
+#include "mod_spdy/common/http_response_parser.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h"  // for SpdyHeaderBlock
+
+namespace mod_spdy {
+
+// Parses incoming HTTP response data and converts it into equivalent SPDY
+// frame data.
+class HttpToSpdyConverter {
+ public:
+  // Interface for the class that will receive frame data from the converter.
+  class SpdyReceiver {
+   public:
+    SpdyReceiver();
+    virtual ~SpdyReceiver();
+
+    // Receive a SYN_REPLY frame with the given headers.  The callee is free to
+    // mutate the headers map (e.g. to add an extra header) before forwarding
+    // it on, but the pointer will not remain valid after this method returns.
+    virtual void ReceiveSynReply(net::SpdyHeaderBlock* headers,
+                                 bool flag_fin) = 0;
+
+    // Receive a DATA frame with the given payload.  The data pointer will not
+    // remain valid after this method returns.
+    virtual void ReceiveData(base::StringPiece data, bool flag_fin) = 0;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(SpdyReceiver);
+  };
+
+  // Create a converter that will send frame data to the given receiver.  The
+  // converter does *not* gain ownership of the receiver.
+  HttpToSpdyConverter(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+  ~HttpToSpdyConverter();
+
+  // Parse and process the next chunk of input; return true on success, false
+  // on failure.
+  bool ProcessInput(base::StringPiece input_data);
+  bool ProcessInput(const char* data, size_t size) {
+    return ProcessInput(base::StringPiece(data, size));
+  }
+
+  // Flush out any buffered data.
+  void Flush();
+
+ private:
+  class ConverterImpl;
+  scoped_ptr<ConverterImpl> impl_;
+  HttpResponseParser parser_;
+
+  DISALLOW_COPY_AND_ASSIGN(HttpToSpdyConverter);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,259 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+#include "base/string_piece.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DeleteArg;
+using testing::Eq;
+using testing::InSequence;
+using testing::Pointee;
+
+namespace {
+
+class MockSpdyReceiver : public mod_spdy::HttpToSpdyConverter::SpdyReceiver {
+ public:
+  MOCK_METHOD2(ReceiveSynReply, void(net::SpdyHeaderBlock* headers,
+                                     bool flag_fin));
+  MOCK_METHOD2(ReceiveData, void(base::StringPiece data, bool flag_fin));
+};
+
+class HttpToSpdyConverterTest :
+      public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+  HttpToSpdyConverterTest() : converter_(GetParam(), &receiver_) {}
+
+ protected:
+  const char* status_header_name() const {
+    return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+            mod_spdy::spdy::kSpdy2Status :
+            mod_spdy::spdy::kSpdy3Status);
+  }
+  const char* version_header_name() const {
+    return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+            mod_spdy::spdy::kSpdy2Version :
+            mod_spdy::spdy::kSpdy3Version);
+  }
+
+  MockSpdyReceiver receiver_;
+  mod_spdy::HttpToSpdyConverter converter_;
+  net::SpdyHeaderBlock expected_headers_;
+};
+
+// Simple response with a small payload.  We should get a SYN_REPLY and a DATA
+// frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithContentLength) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "14";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+  expected_headers_["x-whatever"] = "foobar";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 14\r\n"
+      "Content-Type:      text/plain\r\n"
+      "X-Whatever:foobar\r\n"
+      "\r\n"
+      "Hello, world!\n"
+      "\r\n"));
+}
+
+// The data arrives in two chunks, but they're small, so we should consolidate
+// them into a single DATA frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithChunking) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Connection: Keep-Alive\r\n"
+      "Content-Type: text/plain\r\n"
+      "Keep-Alive: timeout=10, max=5\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "\r\n"
+      "6\r\n"
+      "Hello,\r\n"
+      "8\r\n"
+      " world!\n\r\n"
+      "0\r\n"
+      "\r\n"));
+}
+
+// Test that we don't get tripped up if there is garbage after the end of
+// a chunked message.
+TEST_P(HttpToSpdyConverterTest, ChunkedEncodingWithTrailingGarbage) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Type: text/plain\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "\r\n"
+      "E\r\n"
+      "Hello, world!\n\r\n"
+      "0\r\n"
+      "0\r\n" // multiple last-chunks
+      "\r\n\x1bGaRbAgE"));  // and also some garbage bytes
+}
+
+// No response body, so we should get the FLAG_FIN on the SYN_REPLY, and no
+// DATA frames.
+TEST_P(HttpToSpdyConverterTest, NoResponseBody) {
+  expected_headers_[status_header_name()] = "301";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_["location"] = "https://www.example.com/";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 301 Moved permenantly\r\n"
+      "Location: https://www.example.com/\r\n"
+      "\r\n"));
+}
+
+// Simple response with a large payload.  We should get a SYN_REPLY and
+// multiple DATA frames.
+TEST_P(HttpToSpdyConverterTest, BreakUpLargeDataIntoMultipleFrames) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "10000";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(1808, 'x')), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 10000\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n" +
+      std::string(10000, 'x') +
+      "\r\n"));
+}
+
+// Test that we buffer data until we get the full frame.
+TEST_P(HttpToSpdyConverterTest, BufferUntilWeHaveACompleteFrame) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "4096";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  // Send some of the headers.  We shouldn't get anything out yet.
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 4096\r\n"));
+  // Send the rest of the headers, and some of the data.  We should get the
+  // SYN_REPLY now, but no data yet.
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  ASSERT_TRUE(converter_.ProcessInput(
+      "Content-Type: text/plain\r\n"
+      "\r\n" +
+      std::string(2000, 'x')));
+  // Send some more data, but still not enough for a full frame.
+  ASSERT_TRUE(converter_.ProcessInput(std::string(2000, 'x')));
+  // Send the last of the data.  We should finally get the one DATA frame.
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(true)));
+  ASSERT_TRUE(converter_.ProcessInput(std::string(96, 'x')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, RespectFlushes) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "4096";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  // Send the headers and some of the data (not enough for a full frame).  We
+  // should get the headers out, but no data yet.
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 4096\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n" +
+      std::string(2000, 'x')));
+  // Perform a flush.  We should get the data sent so far.
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2000, 'x')), Eq(false)));
+  converter_.Flush();
+  // Send the rest of the data.  We should get out a second DATA frame, with
+  // FLAG_FIN set.
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2096, 'y')), Eq(true)));
+  ASSERT_TRUE(converter_.ProcessInput(std::string(2096, 'y')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, FlushAfterEndDoesNothing) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "6";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("foobar"), Eq(true)));
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 6\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n"
+      "foobar"));
+  // Flushing after we're done (even multiple times) should be permitted, but
+  // should do nothing.
+  converter_.Flush();
+  converter_.Flush();
+  converter_.Flush();
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, HttpToSpdyConverterTest, testing::Values(
+    mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+    mod_spdy::spdy::SPDY_VERSION_3_1));
+
+}  // namespace

Added: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,146 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/protocol_util.h"
+
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace http {
+
+extern const char* const kAcceptEncoding = "accept-encoding";
+extern const char* const kConnection = "connection";
+extern const char* const kContentLength = "content-length";
+extern const char* const kContentType = "content-type";
+extern const char* const kHost = "host";
+extern const char* const kKeepAlive = "keep-alive";
+extern const char* const kProxyConnection = "proxy-connection";
+extern const char* const kReferer = "referer";
+extern const char* const kTransferEncoding = "transfer-encoding";
+extern const char* const kXAssociatedContent = "x-associated-content";
+extern const char* const kXModSpdy = "x-mod-spdy";
+
+extern const char* const kChunked = "chunked";
+extern const char* const kGzipDeflate = "gzip,deflate";
+
+}  // namespace http
+
+namespace spdy {
+
+extern const char* const kSpdy2Method = "method";
+extern const char* const kSpdy2Scheme = "scheme";
+extern const char* const kSpdy2Status = "status";
+extern const char* const kSpdy2Url = "url";
+extern const char* const kSpdy2Version = "version";
+
+extern const char* const kSpdy3Host = ":host";
+extern const char* const kSpdy3Method = ":method";
+extern const char* const kSpdy3Path = ":path";
+extern const char* const kSpdy3Scheme = ":scheme";
+extern const char* const kSpdy3Status = ":status";
+extern const char* const kSpdy3Version = ":version";
+
+}  // namespace spdy
+
+int SpdyVersionToFramerVersion(spdy::SpdyVersion version) {
+  switch (version) {
+    case spdy::SPDY_VERSION_NONE:
+      return 0;
+    case spdy::SPDY_VERSION_2:
+      return 2;
+    case spdy::SPDY_VERSION_3:
+    case spdy::SPDY_VERSION_3_1:
+      return 3;
+    default:
+      LOG(DFATAL) << "Invalid SpdyVersion value: " << version;
+      return -1;
+  }
+}
+
+const char* SpdyVersionNumberString(spdy::SpdyVersion version) {
+  switch (version) {
+    case spdy::SPDY_VERSION_2:   return "2";
+    case spdy::SPDY_VERSION_3:   return "3";
+    case spdy::SPDY_VERSION_3_1: return "3.1";
+    default:
+      LOG(DFATAL) << "Invalid SpdyVersion value: " << version;
+      return "?";
+  }
+}
+
+const char* GoAwayStatusCodeToString(net::SpdyGoAwayStatus status) {
+  switch (status) {
+    case net::GOAWAY_OK:             return "OK";
+    case net::GOAWAY_PROTOCOL_ERROR: return "PROTOCOL_ERROR";
+    case net::GOAWAY_INTERNAL_ERROR: return "INTERNAL_ERROR";
+    default:                         return "<unknown>";
+  }
+}
+
+const char* SettingsIdToString(net::SpdySettingsIds id) {
+  switch (id) {
+    case net::SETTINGS_UPLOAD_BANDWIDTH:       return "UPLOAD_BANDWIDTH";
+    case net::SETTINGS_DOWNLOAD_BANDWIDTH:     return "DOWNLOAD_BANDWIDTH";
+    case net::SETTINGS_ROUND_TRIP_TIME:        return "ROUND_TRIP_TIME";
+    case net::SETTINGS_MAX_CONCURRENT_STREAMS: return "MAX_CONCURRENT_STREAMS";
+    case net::SETTINGS_CURRENT_CWND:           return "CURRENT_CWND";
+    case net::SETTINGS_DOWNLOAD_RETRANS_RATE:  return "DOWNLOAD_RETRANS_RATE";
+    case net::SETTINGS_INITIAL_WINDOW_SIZE:    return "INITIAL_WINDOW_SIZE";
+    default:                                   return "<unknown>";
+  }
+}
+
+base::StringPiece FrameData(const net::SpdyFrame& frame) {
+  return base::StringPiece(
+      frame.data(), frame.length() + net::SpdyFrame::kHeaderSize);
+}
+
+bool IsInvalidSpdyResponseHeader(base::StringPiece key) {
+  // The following headers are forbidden in SPDY responses (SPDY draft 3
+  // section 3.2.2).
+  return (LowerCaseEqualsASCII(key.begin(), key.end(), http::kConnection) ||
+          LowerCaseEqualsASCII(key.begin(), key.end(), http::kKeepAlive) ||
+          LowerCaseEqualsASCII(key.begin(), key.end(),
+                               http::kProxyConnection) ||
+          LowerCaseEqualsASCII(key.begin(), key.end(),
+                               http::kTransferEncoding));
+}
+
+net::SpdyPriority LowestSpdyPriorityForVersion(
+    spdy::SpdyVersion spdy_version) {
+  return (spdy_version < spdy::SPDY_VERSION_3 ? 3u : 7u);
+}
+
+void MergeInHeader(base::StringPiece key, base::StringPiece value,
+                   net::SpdyHeaderBlock* headers) {
+  // The SPDY spec requires that header names be lowercase, so forcibly
+  // lowercase the key here.
+  std::string lower_key(key.as_string());
+  StringToLowerASCII(&lower_key);
+
+  net::SpdyHeaderBlock::iterator iter = headers->find(lower_key);
+  if (iter == headers->end()) {
+    (*headers)[lower_key] = value.as_string();
+  } else {
+    iter->second.push_back('\0');
+    value.AppendToString(&iter->second);
+  }
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h Thu May  1 11:43:36 2014
@@ -0,0 +1,107 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
+#define MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
+
+#include "base/string_piece.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace http {
+
+// HTTP header names.  These values are all lower-case, so they can be used
+// directly in SPDY header blocks.
+extern const char* const kAcceptEncoding;
+extern const char* const kConnection;
+extern const char* const kContentLength;
+extern const char* const kContentType;
+extern const char* const kHost;
+extern const char* const kKeepAlive;
+extern const char* const kProxyConnection;
+extern const char* const kReferer;
+extern const char* const kTransferEncoding;
+extern const char* const kXAssociatedContent;
+extern const char* const kXModSpdy;
+
+// HTTP header values.
+extern const char* const kChunked;
+extern const char* const kGzipDeflate;
+
+}  // namespace http
+
+namespace spdy {
+
+// Represents a specific SPDY version, including experimental versions such as
+// SPDY/3.1 (which uses version 3 frames, but has extra semantics borrowed from
+// SPDY/4).
+enum SpdyVersion {
+  SPDY_VERSION_NONE,  // not using SPDY
+  SPDY_VERSION_2,     // SPDY/2
+  SPDY_VERSION_3,     // SPDY/3
+  SPDY_VERSION_3_1    // SPDY/3.1 (SPDY/3 framing, but with new flow control)
+};
+
+// Magic header names for SPDY v2.
+extern const char* const kSpdy2Method;
+extern const char* const kSpdy2Scheme;
+extern const char* const kSpdy2Status;
+extern const char* const kSpdy2Url;
+extern const char* const kSpdy2Version;
+
+// Magic header names for SPDY v3.
+extern const char* const kSpdy3Host;
+extern const char* const kSpdy3Method;
+extern const char* const kSpdy3Path;
+extern const char* const kSpdy3Scheme;
+extern const char* const kSpdy3Status;
+extern const char* const kSpdy3Version;
+
+}  // namespace spdy
+
+// Given a SpdyVersion enum value, return the framer version number to use.
+// Returns zero for SPDY_VERSION_NONE.
+int SpdyVersionToFramerVersion(spdy::SpdyVersion version);
+
+// Given a SpdyVersion enum value (other than SPDY_VERSION_NONE), return a
+// string for the version number (e.g. "3" or "3.1").
+const char* SpdyVersionNumberString(spdy::SpdyVersion version);
+
+// Convert various SPDY enum types to strings.
+const char* GoAwayStatusCodeToString(net::SpdyGoAwayStatus status);
+inline const char* RstStreamStatusCodeToString(net::SpdyStatusCodes status) {
+  return net::SpdyFramer::StatusCodeToString(status);
+}
+const char* SettingsIdToString(net::SpdySettingsIds id);
+
+// Return a view of the raw bytes of the frame.
+base::StringPiece FrameData(const net::SpdyFrame& frame);
+
+// Return true if this header is forbidden in SPDY responses (ignoring case).
+bool IsInvalidSpdyResponseHeader(base::StringPiece key);
+
+// Return the SpdyPriority representing the least important priority for the
+// given SPDY version.  For SPDY v2 and below, it's 3; for SPDY v3 and above,
+// it's 7.  (The most important SpdyPriority is always 0.)
+net::SpdyPriority LowestSpdyPriorityForVersion(spdy::SpdyVersion spdy_version);
+
+// Add a header to a header table, lower-casing and merging if necessary.
+void MergeInHeader(base::StringPiece key, base::StringPiece value,
+                   net::SpdyHeaderBlock* headers);
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_PROTOCOL_UTIL_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,95 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/protocol_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(ProtocolUtilTest, InvalidSpdyResponseHeaders) {
+  // Forbidden headers should be rejected regardless of capitalization.
+  EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("connection"));
+  EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("Connection"));
+  EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("cOnNeCtIoN"));
+  EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("transfer-encoding"));
+  EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("Transfer-Encoding"));
+}
+
+TEST(ProtocolUtilTest, ValidSpdyResponseHeaders) {
+  // Permitted headers should be accepted regardless of capitalization (SPDY
+  // requires header names to be lowercase, but this function shouldn't be
+  // checking that).
+  EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader("content-length"));
+  EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader("Content-Length"));
+  EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader(
+      "x-header-we-have-never-heard-of"));
+  EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader(
+      "X-HEADER-WE-HAVE-NEVER-HEARD-OF"));
+}
+
+TEST(ProtocolUtilTest, MergeIntoEmpty) {
+  net::SpdyHeaderBlock headers;
+  ASSERT_EQ(0u, headers.size());
+
+  mod_spdy::MergeInHeader("content-length", "256", &headers);
+  ASSERT_EQ(1u, headers.size());
+  ASSERT_EQ("256", headers["content-length"]);
+}
+
+TEST(ProtocolUtilTest, MakeLowerCase) {
+  net::SpdyHeaderBlock headers;
+  ASSERT_EQ(0u, headers.size());
+
+  mod_spdy::MergeInHeader("Content-Length", "256", &headers);
+  ASSERT_EQ(1u, headers.size());
+  ASSERT_EQ(0u, headers.count("Content-Length"));
+  ASSERT_EQ("256", headers["content-length"]);
+}
+
+TEST(ProtocolUtilTest, MergeDifferentHeaders) {
+  net::SpdyHeaderBlock headers;
+  ASSERT_EQ(0u, headers.size());
+
+  mod_spdy::MergeInHeader("x-foo", "bar", &headers);
+  ASSERT_EQ(1u, headers.size());
+  ASSERT_EQ("bar", headers["x-foo"]);
+
+  mod_spdy::MergeInHeader("x-baz", "quux", &headers);
+  ASSERT_EQ(2u, headers.size());
+  ASSERT_EQ("bar", headers["x-foo"]);
+  ASSERT_EQ("quux", headers["x-baz"]);
+}
+
+TEST(ProtocolUtilTest, MergeRepeatedHeader) {
+  net::SpdyHeaderBlock headers;
+  ASSERT_EQ(0u, headers.size());
+
+  mod_spdy::MergeInHeader("x-foo", "bar", &headers);
+  ASSERT_EQ(1u, headers.size());
+  const std::string expected1("bar");
+  ASSERT_EQ(expected1, headers["x-foo"]);
+
+  mod_spdy::MergeInHeader("x-foo", "baz", &headers);
+  ASSERT_EQ(1u, headers.size());
+  const std::string expected2("bar\0baz", 7);
+  ASSERT_EQ(expected2, headers["x-foo"]);
+
+  mod_spdy::MergeInHeader("x-foo", "quux", &headers);
+  ASSERT_EQ(1u, headers.size());
+  const std::string expected3("bar\0baz\0quux", 12);
+  ASSERT_EQ(expected3, headers["x-foo"]);
+}
+
+}  // namespace

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,120 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+
+#include <list>
+#include <map>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SpdyFramePriorityQueue::SpdyFramePriorityQueue()
+    : condvar_(&lock_) {}
+
+SpdyFramePriorityQueue::~SpdyFramePriorityQueue() {
+  for (QueueMap::iterator iter = queue_map_.begin();
+       iter != queue_map_.end(); ++iter) {
+    FrameList* list = iter->second;
+    STLDeleteContainerPointers(list->begin(), list->end());
+    delete list;
+  }
+}
+
+bool SpdyFramePriorityQueue::IsEmpty() const {
+  base::AutoLock autolock(lock_);
+  return queue_map_.empty();
+}
+
+const int SpdyFramePriorityQueue::kTopPriority = -1;
+
+void SpdyFramePriorityQueue::Insert(int priority, net::SpdyFrame* frame) {
+  base::AutoLock autolock(lock_);
+  DCHECK(frame);
+
+  // Get the frame list for the given priority; if it doesn't currently exist,
+  // create it in the map.
+  FrameList* list = NULL;
+  QueueMap::iterator iter = queue_map_.find(priority);
+  if (iter == queue_map_.end()) {
+    list = new FrameList;
+    queue_map_[priority] = list;
+  } else {
+    list = iter->second;
+  }
+  DCHECK(list);
+
+  // Add the frame to the end of the list, and wake up at most one thread
+  // sleeping on a BlockingPop.
+  list->push_back(frame);
+  condvar_.Signal();
+}
+
+bool SpdyFramePriorityQueue::Pop(net::SpdyFrame** frame) {
+  base::AutoLock autolock(lock_);
+  return InternalPop(frame);
+}
+
+bool SpdyFramePriorityQueue::BlockingPop(const base::TimeDelta& max_time,
+                                         net::SpdyFrame** frame) {
+  base::AutoLock autolock(lock_);
+  DCHECK(frame);
+
+  const base::TimeDelta zero = base::TimeDelta();
+  base::TimeDelta time_remaining = max_time;
+  while (time_remaining > zero && queue_map_.empty()) {
+    // TODO(mdsteele): It appears from looking at the Chromium source code that
+    // HighResNow() is "expensive" on Windows (how expensive, I am not sure);
+    // however, the other options for getting a "now" time either don't
+    // guarantee monotonicity (so time might go backwards) or might be too
+    // low-resolution for our purposes, so I think we'd better stick with this
+    // for now.  But is there a better way to do what we're doing here?
+    const base::TimeTicks start = base::TimeTicks::HighResNow();
+    condvar_.TimedWait(time_remaining);
+    time_remaining -= base::TimeTicks::HighResNow() - start;
+  }
+
+  return InternalPop(frame);
+}
+
+bool SpdyFramePriorityQueue::InternalPop(net::SpdyFrame** frame) {
+  lock_.AssertAcquired();
+  DCHECK(frame);
+  if (queue_map_.empty()) {
+    return false;
+  }
+  // As an invariant, the lists in the queue map are never empty.  So get the
+  // list of highest priority (smallest priority number) and pop the first
+  // frame from it.
+  QueueMap::iterator iter = queue_map_.begin();
+  FrameList* list = iter->second;
+  DCHECK(!list->empty());
+  *frame = list->front();
+  list->pop_front();
+  // If the list is now empty, we have to delete it from the map to maintain
+  // the invariant.
+  if (list->empty()) {
+    queue_map_.erase(iter);
+    delete list;
+  }
+  return true;
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h Thu May  1 11:43:36 2014
@@ -0,0 +1,95 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
+#define MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace base { class TimeDelta; }
+
+namespace net { class SpdyFrame; }
+
+namespace mod_spdy {
+
+// A priority queue of SPDY frames, intended for multiplexing output frames
+// from multiple SPDY stream threads back to the SPDY connection thread and
+// allowing frames from high-priority streams to cut in front of lower-priority
+// streams.  This class is thread-safe -- its methods may be called
+// concurrently by multiple threads.
+class SpdyFramePriorityQueue {
+ public:
+  // Create an initially-empty queue.
+  SpdyFramePriorityQueue();
+  ~SpdyFramePriorityQueue();
+
+  // Return true if the queue is currently empty.  (Of course, there's no
+  // guarantee that another thread won't change that as soon as this method
+  // returns.)
+  bool IsEmpty() const;
+
+  // A priority value that is more important than any priority normally used
+  // for sending SPDY frames.
+  static const int kTopPriority;
+
+  // Insert a frame into the queue at the specified priority.  The queue takes
+  // ownership of the frame, and will delete it if the queue is deleted before
+  // the frame is removed from the queue by the Pop method.  Note that smaller
+  // numbers indicate higher priorities.
+  void Insert(int priority, net::SpdyFrame* frame);
+
+  // Remove and provide a frame from the queue and return true, or return false
+  // if the queue is empty.  The caller gains ownership of the provided frame
+  // object.  This method will try to yield higher-priority frames before
+  // lower-priority ones (even if they were inserted later), but guarantees to
+  // return same-priority frames in the same order they were inserted (FIFO).
+  // In particular, this means that a sequence of frames from the same SPDY
+  // stream will stay in order (assuming they were all inserted with the same
+  // priority -- that of the stream).
+  bool Pop(net::SpdyFrame** frame);
+
+  // Like Pop(), but if the queue is empty this method will block for up to
+  // max_time before returning false.
+  bool BlockingPop(const base::TimeDelta& max_time, net::SpdyFrame** frame);
+
+ private:
+  // Same as Pop(), but requires lock_ to be held.
+  bool InternalPop(net::SpdyFrame** frame);
+
+  mutable base::Lock lock_;
+  base::ConditionVariable condvar_;
+  // We use a map of lists to store frames, to guarantee that frames of the
+  // same priority are stored in FIFO order.  A simpler implementation would be
+  // to just use a multimap, which in practice is nearly always implemented
+  // with the FIFO behavior that we want, but the spec doesn't actually
+  // guarantee that behavior.
+  //
+  // Each list stores frames of a particular priority.  Invariant: the lists in
+  // the QueueMap are never empty; if one of the lists becomes empty, that
+  // key/value pair is immediately removed from the map.
+  typedef std::list<net::SpdyFrame*> FrameList;
+  typedef std::map<int, FrameList*> QueueMap;
+  QueueMap queue_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyFramePriorityQueue);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,147 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+
+#include "base/time.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+net::SpdyStreamId GetPingId(net::SpdyFrame* frame) {
+  if (!frame->is_control_frame() ||
+      static_cast<net::SpdyControlFrame*>(frame)->type() != net::PING) {
+    ADD_FAILURE() << "Frame is not a PING frame.";
+    return 0;
+  }
+  return static_cast<net::SpdyPingControlFrame*>(frame)->unique_id();
+}
+
+void ExpectPop(net::SpdyStreamId expected,
+               mod_spdy::SpdyFramePriorityQueue* queue) {
+  EXPECT_FALSE(queue->IsEmpty());
+  net::SpdyFrame* raw_frame = NULL;
+  const bool success = queue->Pop(&raw_frame);
+  scoped_ptr<net::SpdyFrame> scoped_frame(raw_frame);
+  EXPECT_TRUE(success);
+  ASSERT_TRUE(scoped_frame != NULL);
+  ASSERT_EQ(expected, GetPingId(scoped_frame.get()));
+}
+
+void ExpectEmpty(mod_spdy::SpdyFramePriorityQueue* queue) {
+  EXPECT_TRUE(queue->IsEmpty());
+  net::SpdyFrame* frame = NULL;
+  EXPECT_FALSE(queue->Pop(&frame));
+  EXPECT_TRUE(frame == NULL);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertSpdy2) {
+  net::SpdyFramer framer(2);
+  mod_spdy::SpdyFramePriorityQueue queue;
+  ExpectEmpty(&queue);
+
+  EXPECT_EQ(3u, framer.GetLowestPriority());
+  EXPECT_EQ(0u, framer.GetHighestPriority());
+
+  queue.Insert(3, framer.CreatePingFrame(4));
+  queue.Insert(0, framer.CreatePingFrame(1));
+  queue.Insert(3, framer.CreatePingFrame(3));
+
+  ExpectPop(1, &queue);
+  ExpectPop(4, &queue);
+
+  queue.Insert(2, framer.CreatePingFrame(2));
+  queue.Insert(1, framer.CreatePingFrame(6));
+  queue.Insert(1, framer.CreatePingFrame(5));
+
+  ExpectPop(6, &queue);
+  ExpectPop(5, &queue);
+  ExpectPop(2, &queue);
+  ExpectPop(3, &queue);
+  ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertSpdy3) {
+  net::SpdyFramer framer(3);
+  mod_spdy::SpdyFramePriorityQueue queue;
+  ExpectEmpty(&queue);
+
+  EXPECT_EQ(7u, framer.GetLowestPriority());
+  EXPECT_EQ(0u, framer.GetHighestPriority());
+
+  queue.Insert(7, framer.CreatePingFrame(4));
+  queue.Insert(0, framer.CreatePingFrame(1));
+  queue.Insert(7, framer.CreatePingFrame(3));
+
+  ExpectPop(1, &queue);
+  ExpectPop(4, &queue);
+
+  queue.Insert(6, framer.CreatePingFrame(2));
+  queue.Insert(1, framer.CreatePingFrame(6));
+  queue.Insert(5, framer.CreatePingFrame(5));
+
+  ExpectPop(6, &queue);
+  ExpectPop(5, &queue);
+  ExpectPop(2, &queue);
+  ExpectPop(3, &queue);
+  ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertTopPriority) {
+  net::SpdyFramer framer(2);
+  mod_spdy::SpdyFramePriorityQueue queue;
+  ExpectEmpty(&queue);
+
+  queue.Insert(3, framer.CreatePingFrame(4));
+  queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+               framer.CreatePingFrame(2));
+  queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+               framer.CreatePingFrame(6));
+  queue.Insert(0, framer.CreatePingFrame(1));
+  queue.Insert(3, framer.CreatePingFrame(3));
+
+  ExpectPop(2, &queue);
+  ExpectPop(6, &queue);
+  ExpectPop(1, &queue);
+  ExpectPop(4, &queue);
+
+  queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+               framer.CreatePingFrame(5));
+
+  ExpectPop(5, &queue);
+  ExpectPop(3, &queue);
+  ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, BlockingPop) {
+  mod_spdy::SpdyFramePriorityQueue queue;
+  net::SpdyFrame* frame;
+  ASSERT_FALSE(queue.Pop(&frame));
+
+  const base::TimeDelta time_to_wait = base::TimeDelta::FromMilliseconds(50);
+  const base::TimeTicks start = base::TimeTicks::HighResNow();
+  ASSERT_FALSE(queue.BlockingPop(time_to_wait, &frame));
+  const base::TimeDelta actual_time_waited =
+      base::TimeTicks::HighResNow() - start;
+
+  // Check that we waited at least as long as we asked for.
+  EXPECT_GE(actual_time_waited, time_to_wait);
+  // Check that we didn't wait too much longer than we asked for.
+  EXPECT_LT(actual_time_waited.InMillisecondsF(),
+            1.1 * time_to_wait.InMillisecondsF());
+}
+
+}  // namespace

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,84 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_queue.h"
+
+#include <list>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SpdyFrameQueue::SpdyFrameQueue()
+    : condvar_(&lock_), is_aborted_(false) {}
+
+SpdyFrameQueue::~SpdyFrameQueue() {
+  STLDeleteContainerPointers(queue_.begin(), queue_.end());
+}
+
+bool SpdyFrameQueue::is_aborted() const {
+  base::AutoLock autolock(lock_);
+  return is_aborted_;
+}
+
+void SpdyFrameQueue::Abort() {
+  base::AutoLock autolock(lock_);
+  is_aborted_ = true;
+  STLDeleteContainerPointers(queue_.begin(), queue_.end());
+  queue_.clear();
+  condvar_.Broadcast();
+}
+
+void SpdyFrameQueue::Insert(net::SpdyFrame* frame) {
+  base::AutoLock autolock(lock_);
+  DCHECK(frame);
+
+  if (is_aborted_) {
+    DCHECK(queue_.empty());
+    delete frame;
+  } else {
+    if (queue_.empty()) {
+      condvar_.Signal();
+    }
+    queue_.push_front(frame);
+  }
+}
+
+bool SpdyFrameQueue::Pop(bool block, net::SpdyFrame** frame) {
+  base::AutoLock autolock(lock_);
+  DCHECK(frame);
+
+  if (block) {
+    // Block until the queue is nonempty or we abort.
+    while (queue_.empty() && !is_aborted_) {
+      condvar_.Wait();
+    }
+  }
+
+  // If we've aborted, the queue should now be empty.
+  DCHECK(!is_aborted_ || queue_.empty());
+  if (queue_.empty()) {
+    return false;
+  }
+
+  *frame = queue_.back();
+  queue_.pop_back();
+  return true;
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h Thu May  1 11:43:36 2014
@@ -0,0 +1,71 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
+#define MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace net { class SpdyFrame; }
+
+namespace mod_spdy {
+
+// A simple FIFO queue of SPDY frames, intended for sending input frames from
+// the SPDY connection thread to a SPDY stream thread.  This class is
+// thread-safe -- all methods may be called concurrently by multiple threads.
+class SpdyFrameQueue {
+ public:
+  // Create an initially-empty queue.
+  SpdyFrameQueue();
+  ~SpdyFrameQueue();
+
+  // Return true if this queue has been aborted.
+  bool is_aborted() const;
+
+  // Abort the queue.  All frames held by the queue will be deleted; future
+  // frames passed to Insert() will be immediately deleted; future calls to
+  // Pop() will fail immediately; and current blocking calls to Pop will
+  // immediately unblock and fail.
+  void Abort();
+
+  // Insert a frame into the queue.  The queue takes ownership of the frame,
+  // and will delete it if the queue is deleted or aborted before the frame is
+  // removed from the queue by the Pop method.
+  void Insert(net::SpdyFrame* frame);
+
+  // Remove and provide a frame from the queue and return true, or return false
+  // if the queue is empty or has been aborted.  If the block argument is true,
+  // block until a frame becomes available (or the queue is aborted).  The
+  // caller gains ownership of the provided frame object.
+  bool Pop(bool block, net::SpdyFrame** frame);
+
+ private:
+  // This is a pretty naive implementation of a thread-safe queue, but it's
+  // good enough for our purposes.  We could use an apr_queue_t instead of
+  // rolling our own class, but it lacks the ownership semantics that we want.
+  mutable base::Lock lock_;
+  base::ConditionVariable condvar_;
+  std::list<net::SpdyFrame*> queue_;
+  bool is_aborted_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyFrameQueue);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,123 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_queue.h"
+
+#include "base/basictypes.h"
+#include "base/threading/platform_thread.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int kSpdyVersion = 2;
+
+net::SpdyStreamId GetPingId(net::SpdyFrame* frame) {
+  if (!frame->is_control_frame() ||
+      static_cast<net::SpdyControlFrame*>(frame)->type() != net::PING) {
+    ADD_FAILURE() << "Frame is not a PING frame.";
+    return 0;
+  }
+  return static_cast<net::SpdyPingControlFrame*>(frame)->unique_id();
+}
+
+void ExpectPop(bool block, net::SpdyStreamId expected,
+               mod_spdy::SpdyFrameQueue* queue) {
+  net::SpdyFrame* raw_frame = NULL;
+  const bool success = queue->Pop(block, &raw_frame);
+  scoped_ptr<net::SpdyFrame> scoped_frame(raw_frame);
+  EXPECT_TRUE(success);
+  ASSERT_TRUE(scoped_frame != NULL);
+  ASSERT_EQ(expected, GetPingId(scoped_frame.get()));
+}
+
+void ExpectEmpty(mod_spdy::SpdyFrameQueue* queue) {
+  net::SpdyFrame* frame = NULL;
+  EXPECT_FALSE(queue->Pop(false, &frame));
+  EXPECT_TRUE(frame == NULL);
+}
+
+TEST(SpdyFrameQueueTest, Simple) {
+  net::SpdyFramer framer(kSpdyVersion);
+  mod_spdy::SpdyFrameQueue queue;
+  ExpectEmpty(&queue);
+
+  queue.Insert(framer.CreatePingFrame(4));
+  queue.Insert(framer.CreatePingFrame(1));
+  queue.Insert(framer.CreatePingFrame(3));
+
+  ExpectPop(false, 4, &queue);
+  ExpectPop(false, 1, &queue);
+
+  queue.Insert(framer.CreatePingFrame(2));
+  queue.Insert(framer.CreatePingFrame(5));
+
+  ExpectPop(false, 3, &queue);
+  ExpectPop(false, 2, &queue);
+  ExpectPop(false, 5, &queue);
+  ExpectEmpty(&queue);
+}
+
+TEST(SpdyFrameQueueTest, AbortEmptiesQueue) {
+  net::SpdyFramer framer(kSpdyVersion);
+  mod_spdy::SpdyFrameQueue queue;
+  ASSERT_FALSE(queue.is_aborted());
+  ExpectEmpty(&queue);
+
+  queue.Insert(framer.CreatePingFrame(4));
+  queue.Insert(framer.CreatePingFrame(1));
+  queue.Insert(framer.CreatePingFrame(3));
+
+  ExpectPop(false, 4, &queue);
+
+  queue.Abort();
+
+  ExpectEmpty(&queue);
+  ASSERT_TRUE(queue.is_aborted());
+}
+
+class BlockingPopTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+  explicit BlockingPopTask(mod_spdy::SpdyFrameQueue* queue) : queue_(queue) {}
+  virtual void Run() { ExpectPop(true, 7, queue_); }
+ private:
+  mod_spdy::SpdyFrameQueue* const queue_;
+  DISALLOW_COPY_AND_ASSIGN(BlockingPopTask);
+};
+
+TEST(SpdyFrameQueueTest, BlockingPop) {
+  net::SpdyFramer framer(kSpdyVersion);
+  mod_spdy::SpdyFrameQueue queue;
+
+  // Start a task that will do a blocking pop from the queue.
+  mod_spdy::testing::AsyncTaskRunner runner(new BlockingPopTask(&queue));
+  ASSERT_TRUE(runner.Start());
+
+  // Even if we wait for a little bit, the task shouldn't complete, because
+  // that thread is blocked, because the queue is still empty.
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+  runner.notification()->ExpectNotSet();
+  ExpectEmpty(&queue);
+
+  // Now, if we push something into the queue, the task should soon unblock and
+  // complete, and the queue should then be empty.
+  queue.Insert(framer.CreatePingFrame(7));
+  runner.notification()->ExpectSetWithinMillis(100);
+  ExpectEmpty(&queue);
+}
+
+}  // namespace

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,65 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_server_config.h"
+
+#include "mod_spdy/common/protocol_util.h"
+
+namespace {
+
+const bool kDefaultSpdyEnabled = false;
+const int kDefaultMaxStreamsPerConnection = 100;
+const int kDefaultMinThreadsPerProcess = 2;
+const int kDefaultMaxThreadsPerProcess = 10;
+const int kDefaultMaxServerPushDepth = 1;
+const bool kDefaultSendVersionHeader = true;
+const mod_spdy::spdy::SpdyVersion kDefaultUseSpdyVersionWithoutSsl =
+    mod_spdy::spdy::SPDY_VERSION_NONE;
+const int kDefaultVlogLevel = 0;
+
+}  // namespace
+
+namespace mod_spdy {
+
+SpdyServerConfig::SpdyServerConfig()
+    : spdy_enabled_(kDefaultSpdyEnabled),
+      max_streams_per_connection_(kDefaultMaxStreamsPerConnection),
+      min_threads_per_process_(kDefaultMinThreadsPerProcess),
+      max_threads_per_process_(kDefaultMaxThreadsPerProcess),
+      max_server_push_depth_(kDefaultMaxServerPushDepth),
+      send_version_header_(kDefaultSendVersionHeader),
+      use_spdy_version_without_ssl_(kDefaultUseSpdyVersionWithoutSsl),
+      vlog_level_(kDefaultVlogLevel) {}
+
+SpdyServerConfig::~SpdyServerConfig() {}
+
+void SpdyServerConfig::MergeFrom(const SpdyServerConfig& a,
+                                 const SpdyServerConfig& b) {
+  spdy_enabled_.MergeFrom(a.spdy_enabled_, b.spdy_enabled_);
+  max_streams_per_connection_.MergeFrom(a.max_streams_per_connection_,
+                                        b.max_streams_per_connection_);
+  min_threads_per_process_.MergeFrom(a.min_threads_per_process_,
+                                     b.min_threads_per_process_);
+  max_threads_per_process_.MergeFrom(a.max_threads_per_process_,
+                                     b.max_threads_per_process_);
+  max_server_push_depth_.MergeFrom(a.max_server_push_depth_,
+                                   b.max_server_push_depth_);
+  send_version_header_.MergeFrom(
+      a.send_version_header_, b.send_version_header_);
+  use_spdy_version_without_ssl_.MergeFrom(
+      a.use_spdy_version_without_ssl_, b.use_spdy_version_without_ssl_);
+  vlog_level_.MergeFrom(a.vlog_level_, b.vlog_level_);
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h Thu May  1 11:43:36 2014
@@ -0,0 +1,121 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SERVER_CONFIG_H_
+#define MOD_SPDY_COMMON_SPDY_SERVER_CONFIG_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace mod_spdy {
+
+// Stores server configuration settings for our module.
+class SpdyServerConfig {
+ public:
+  SpdyServerConfig();
+  ~SpdyServerConfig();
+
+  // Return true if SPDY is enabled for this server, false otherwise.
+  bool spdy_enabled() const { return spdy_enabled_.get(); }
+
+  // Return the maximum number of simultaneous SPDY streams that should be
+  // permitted for a single client connection.
+  int max_streams_per_connection() const {
+    return max_streams_per_connection_.get();
+  }
+
+  // Return the minimum number of worker threads to spawn per child process.
+  int min_threads_per_process() const {
+    return min_threads_per_process_.get();
+  }
+
+  // Return the maximum number of worker threads to spawn per child process.
+  int max_threads_per_process() const {
+    return max_threads_per_process_.get();
+  }
+
+  // Return the maximum number of recursive levels to follow
+  // X-Associated-Content headers
+  int max_server_push_depth() const {
+    return max_server_push_depth_.get();
+  }
+
+  // Whether or not we should include an x-mod-spdy header with the module
+  // version number.
+  bool send_version_header() const { return send_version_header_.get(); }
+
+  // If nonzero, assume (unencrypted) SPDY/x for non-SSL connections, where x
+  // is the version number returned here.  This will most likely break normal
+  // browsers, but is useful for testing.
+  spdy::SpdyVersion use_spdy_version_without_ssl() const {
+    return use_spdy_version_without_ssl_.get();
+  }
+
+  // Return the maximum VLOG level we should use.
+  int vlog_level() const { return vlog_level_.get(); }
+
+  // Setters.  Call only during the configuration phase.
+  void set_spdy_enabled(bool b) { spdy_enabled_.set(b); }
+  void set_max_streams_per_connection(int n) {
+    max_streams_per_connection_.set(n);
+  }
+  void set_min_threads_per_process(int n) { min_threads_per_process_.set(n); }
+  void set_max_threads_per_process(int n) { max_threads_per_process_.set(n); }
+  void set_max_server_push_depth(int n) { max_server_push_depth_.set(n); }
+  void set_send_version_header(bool b) { send_version_header_.set(b); }
+  void set_use_spdy_version_without_ssl(spdy::SpdyVersion v) {
+    use_spdy_version_without_ssl_.set(v);
+  }
+  void set_vlog_level(int n) { vlog_level_.set(n); }
+
+  // Set this config object to the merge of a and b.  Call only during the
+  // configuration phase.
+  void MergeFrom(const SpdyServerConfig& a, const SpdyServerConfig& b);
+
+ private:
+  template <typename T>
+  class Option {
+   public:
+    explicit Option(const T& default_value)
+        : was_set_(false), value_(default_value) {}
+    const T& get() const { return value_; }
+    void set(const T& value) { was_set_ = true; value_ = value; }
+    void MergeFrom(const Option<T>& a, const Option<T>& b) {
+      was_set_ = a.was_set_ || b.was_set_;
+      value_ = a.was_set_ ? a.value_ : b.value_;
+    }
+   private:
+    bool was_set_;
+    T value_;
+    DISALLOW_COPY_AND_ASSIGN(Option);
+  };
+
+  // Configuration fields:
+  Option<bool> spdy_enabled_;
+  Option<int> max_streams_per_connection_;
+  Option<int> min_threads_per_process_;
+  Option<int> max_threads_per_process_;
+  Option<int> max_server_push_depth_;
+  Option<bool> send_version_header_;
+  Option<spdy::SpdyVersion> use_spdy_version_without_ssl_;
+  Option<int> vlog_level_;
+  // Note: Add more config options here as needed; be sure to also update the
+  //   MergeFrom method in spdy_server_config.cc.
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyServerConfig);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_CONTEXT_SPDY_SERVER_CONFIG_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc Thu May  1 11:43:36 2014
@@ -0,0 +1,23 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_server_push_interface.h"
+
+namespace mod_spdy {
+
+SpdyServerPushInterface::SpdyServerPushInterface() {}
+
+SpdyServerPushInterface::~SpdyServerPushInterface() {}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h Thu May  1 11:43:36 2014
@@ -0,0 +1,66 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
+#define MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class SpdyServerPushInterface {
+ public:
+  SpdyServerPushInterface();
+  virtual ~SpdyServerPushInterface();
+
+  enum PushStatus {
+    // PUSH_STARTED: The server push was started successfully.
+    PUSH_STARTED,
+    // INVALID_REQUEST_HEADERS: The given request headers were invalid for a
+    // server push (e.g. because required headers were missing).
+    INVALID_REQUEST_HEADERS,
+    // ASSOCIATED_STREAM_INACTIVE: The push could not be started because the
+    // associated stream is not currently active.
+    ASSOCIATED_STREAM_INACTIVE,
+    // CANNOT_PUSH_EVER_AGAIN: We can't do any more pushes on this session,
+    // either because the client has already sent us a GOAWAY frame, or the
+    // session has been open so long that we've run out of stream IDs.
+    CANNOT_PUSH_EVER_AGAIN,
+    // TOO_MANY_CONCURRENT_PUSHES: The push could not be started right now
+    // because there are too many currently active push streams.
+    TOO_MANY_CONCURRENT_PUSHES,
+    // PUSH_INTERNAL_ERROR: There was an internal error in the SpdySession
+    // (typically something that caused a LOG(DFATAL).
+    PUSH_INTERNAL_ERROR,
+  };
+
+  // Initiate a SPDY server push, roughly by pretending that the client sent a
+  // SYN_STREAM with the given headers.  To repeat: the headers argument is
+  // _not_ the headers that the server will send to the client, but rather the
+  // headers to _pretend_ that the client sent to the server.
+  virtual PushStatus StartServerPush(
+      net::SpdyStreamId associated_stream_id,
+      int32 server_push_depth,
+      net::SpdyPriority priority,
+      const net::SpdyHeaderBlock& request_headers) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SpdyServerPushInterface);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_

Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h
------------------------------------------------------------------------------
    svn:eol-style = native