You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2014/05/01 13:39:32 UTC

svn commit: r1591620 [6/14] - in /httpd/mod_spdy/branches/httpd-2.2.x: ./ base/ base/metrics/ build/ install/ install/common/ install/debian/ install/rpm/ mod_spdy/ mod_spdy/apache/ mod_spdy/apache/filters/ mod_spdy/apache/testing/ mod_spdy/common/ mod...

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.cc Thu May  1 11:39:27 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/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+void OnHeader(const base::StringPiece& key,
+              const base::StringPiece& value,
+              std::string* output) {
+  key.AppendToString(output);
+  output->append(": ");
+  value.AppendToString(output);
+  output->append("\r\n");
+}
+
+}  // namespace
+
+namespace mod_spdy {
+
+HttpStringBuilder::HttpStringBuilder(std::string* str)
+    : string_(str), state_(REQUEST_LINE) {
+  CHECK(string_);
+}
+
+HttpStringBuilder::~HttpStringBuilder() {}
+
+void HttpStringBuilder::OnRequestLine(const base::StringPiece& method,
+                                      const base::StringPiece& path,
+                                      const base::StringPiece& version) {
+  DCHECK(state_ == REQUEST_LINE);
+  state_ = LEADING_HEADERS;
+  method.AppendToString(string_);
+  string_->push_back(' ');
+  path.AppendToString(string_);
+  string_->push_back(' ');
+  version.AppendToString(string_);
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnLeadingHeader(const base::StringPiece& key,
+                                        const base::StringPiece& value) {
+  DCHECK(state_ == LEADING_HEADERS);
+  OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnLeadingHeadersComplete() {
+  DCHECK(state_ == LEADING_HEADERS);
+  state_ = LEADING_HEADERS_COMPLETE;
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnRawData(const base::StringPiece& data) {
+  DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == RAW_DATA);
+  state_ = RAW_DATA;
+  data.AppendToString(string_);
+}
+
+void HttpStringBuilder::OnDataChunk(const base::StringPiece& data) {
+  DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == DATA_CHUNKS);
+  state_ = DATA_CHUNKS;
+  // Encode the data as an HTTP data chunk.  See RFC 2616 section 3.6.1 for
+  // details.
+  base::StringAppendF(string_, "%lX\r\n",
+                      static_cast<unsigned long>(data.size()));
+  data.AppendToString(string_);
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnDataChunksComplete() {
+  DCHECK(state_ == DATA_CHUNKS);
+  state_ = DATA_CHUNKS_COMPLETE;
+  // Indicate that there are no more HTTP data chunks coming.  See RFC 2616
+  // section 3.6.1 for details.
+  string_->append("0\r\n");
+}
+
+void HttpStringBuilder::OnTrailingHeader(const base::StringPiece& key,
+                                         const base::StringPiece& value) {
+  DCHECK(state_ == DATA_CHUNKS_COMPLETE || state_ == TRAILING_HEADERS);
+  state_ = TRAILING_HEADERS;
+  OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnTrailingHeadersComplete() {
+  DCHECK(state_ == TRAILING_HEADERS);
+  state_ = TRAILING_HEADERS_COMPLETE;
+  string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnComplete() {
+  DCHECK(state_ == LEADING_HEADERS_COMPLETE ||
+         state_ == RAW_DATA ||
+         state_ == DATA_CHUNKS_COMPLETE ||
+         state_ == TRAILING_HEADERS_COMPLETE);
+  if (state_ == DATA_CHUNKS_COMPLETE) {
+    // In this case, there have been data chunks, but we haven't called
+    // OnTrailingHeadersComplete because there were no trailing headers.  We
+    // still need an empty line to indicate the end of the request.
+    string_->append("\r\n");
+  }
+  state_ = COMPLETE;
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.h Thu May  1 11:39:27 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/branches/httpd-2.2.x/mod_spdy/common/http_string_builder.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.cc Thu May  1 11:39:27 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/strings/string_piece.h"
+#include "mod_spdy/common/http_response_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// This is the number of bytes we want to send per data frame.  We never send
+// data frames larger than this, but we might send smaller ones if we have to
+// flush early.
+// TODO The SPDY folks say that smallish (~4kB) data frames are good; however,
+//      we should experiment later on to see what value here performs the best.
+const size_t kTargetDataFrameBytes = 4096;
+
+}  // namespace
+
+namespace mod_spdy {
+
+class HttpToSpdyConverter::ConverterImpl : public HttpResponseVisitorInterface{
+ public:
+  ConverterImpl(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+  virtual ~ConverterImpl();
+
+  void Flush();
+
+  // HttpResponseVisitorInterface methods:
+  virtual void OnStatusLine(const base::StringPiece& version,
+                            const base::StringPiece& status_code,
+                            const base::StringPiece& status_phrase);
+  virtual void OnLeadingHeader(const base::StringPiece& key,
+                               const base::StringPiece& value);
+  virtual void OnLeadingHeadersComplete(bool fin);
+  virtual void OnData(const base::StringPiece& data, bool fin);
+
+ private:
+  void SendDataIfNecessary(bool flush, bool fin);
+  void SendDataFrame(const char* data, size_t size, bool flag_fin);
+
+  const spdy::SpdyVersion spdy_version_;
+  SpdyReceiver* const receiver_;
+  net::SpdyHeaderBlock headers_;
+  std::string data_buffer_;
+  bool sent_flag_fin_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConverterImpl);
+};
+
+HttpToSpdyConverter::SpdyReceiver::SpdyReceiver() {}
+
+HttpToSpdyConverter::SpdyReceiver::~SpdyReceiver() {}
+
+HttpToSpdyConverter::HttpToSpdyConverter(spdy::SpdyVersion spdy_version,
+                                         SpdyReceiver* receiver)
+    : impl_(new ConverterImpl(spdy_version, receiver)),
+      parser_(impl_.get()) {}
+
+HttpToSpdyConverter::~HttpToSpdyConverter() {}
+
+bool HttpToSpdyConverter::ProcessInput(base::StringPiece input_data) {
+  return parser_.ProcessInput(input_data);
+}
+
+void HttpToSpdyConverter::Flush() {
+  impl_->Flush();
+}
+
+HttpToSpdyConverter::ConverterImpl::ConverterImpl(
+    spdy::SpdyVersion spdy_version, SpdyReceiver* receiver)
+    : spdy_version_(spdy_version),
+      receiver_(receiver),
+      sent_flag_fin_(false) {
+  DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+  CHECK(receiver_);
+}
+
+HttpToSpdyConverter::ConverterImpl::~ConverterImpl() {}
+
+void HttpToSpdyConverter::ConverterImpl::Flush() {
+  SendDataIfNecessary(true,  // true = do flush
+                      false);  // false = not fin yet
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnStatusLine(
+    const base::StringPiece& version,
+    const base::StringPiece& status_code,
+    const base::StringPiece& status_phrase) {
+  DCHECK(headers_.empty());
+  const bool spdy2 = spdy_version_ < spdy::SPDY_VERSION_3;
+  headers_[spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version] =
+      version.as_string();
+  headers_[spdy2 ? spdy::kSpdy2Status : spdy::kSpdy3Status] =
+      status_code.as_string();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeader(
+    const base::StringPiece& key,
+    const base::StringPiece& value) {
+  // Filter out headers that are invalid in SPDY.
+  if (IsInvalidSpdyResponseHeader(key)) {
+    return;
+  }
+  MergeInHeader(key, value, &headers_);
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeadersComplete(bool fin) {
+  if (sent_flag_fin_) {
+    LOG(DFATAL) << "Trying to send headers after sending FLAG_FIN";
+    return;
+  }
+  if (fin) {
+    sent_flag_fin_ = true;
+  }
+  receiver_->ReceiveSynReply(&headers_, fin);
+  headers_.clear();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnData(const base::StringPiece& data,
+                                                bool fin) {
+  data.AppendToString(&data_buffer_);
+  SendDataIfNecessary(false, fin);  // false = don't flush
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataIfNecessary(bool flush,
+                                                             bool fin) {
+  // If we have (strictly) more than one frame's worth of data waiting, send it
+  // down the filter chain, kTargetDataFrameBytes bytes at a time.  If we are
+  // left with _exactly_ kTargetDataFrameBytes bytes of data, we'll deal with
+  // that in the next code block (see the comment there to explain why).
+  if (data_buffer_.size() > kTargetDataFrameBytes) {
+    const char* start = data_buffer_.data();
+    size_t size = data_buffer_.size();
+    while (size > kTargetDataFrameBytes) {
+      SendDataFrame(start, kTargetDataFrameBytes, false);
+      start += kTargetDataFrameBytes;
+      size -= kTargetDataFrameBytes;
+    }
+    data_buffer_.erase(0, data_buffer_.size() - size);
+  }
+  DCHECK(data_buffer_.size() <= kTargetDataFrameBytes);
+
+  // We may still have some leftover data.  We need to send another data frame
+  // now (rather than waiting for a full kTargetDataFrameBytes) if:
+  //   1) This is the end of the response,
+  //   2) we're supposed to flush and the buffer is nonempty, or
+  //   3) we still have a full data frame's worth in the buffer.
+  //
+  // Note that because of the previous code block, condition (3) will only be
+  // true if we have exactly kTargetDataFrameBytes of data.  However, dealing
+  // with that case here instead of in the above block makes it easier to make
+  // sure we correctly set FLAG_FIN on the final data frame, which is why the
+  // above block uses a strict, > comparison rather than a non-strict, >=
+  // comparison.
+  if (fin || (flush && !data_buffer_.empty()) ||
+      data_buffer_.size() >= kTargetDataFrameBytes) {
+    SendDataFrame(data_buffer_.data(), data_buffer_.size(), fin);
+    data_buffer_.clear();
+  }
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataFrame(
+    const char* data, size_t size, bool flag_fin) {
+  if (sent_flag_fin_) {
+    LOG(DFATAL) << "Trying to send data after sending FLAG_FIN";
+    return;
+  }
+  if (flag_fin) {
+    sent_flag_fin_ = true;
+  }
+  receiver_->ReceiveData(base::StringPiece(data, size), flag_fin);
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.h Thu May  1 11:39:27 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/strings/string_piece.h"
+#include "mod_spdy/common/http_response_parser.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h"  // for SpdyHeaderBlock
+
+namespace mod_spdy {
+
+// Parses incoming HTTP response data and converts it into equivalent SPDY
+// frame data.
+class HttpToSpdyConverter {
+ public:
+  // Interface for the class that will receive frame data from the converter.
+  class SpdyReceiver {
+   public:
+    SpdyReceiver();
+    virtual ~SpdyReceiver();
+
+    // Receive a SYN_REPLY frame with the given headers.  The callee is free to
+    // mutate the headers map (e.g. to add an extra header) before forwarding
+    // it on, but the pointer will not remain valid after this method returns.
+    virtual void ReceiveSynReply(net::SpdyHeaderBlock* headers,
+                                 bool flag_fin) = 0;
+
+    // Receive a DATA frame with the given payload.  The data pointer will not
+    // remain valid after this method returns.
+    virtual void ReceiveData(base::StringPiece data, bool flag_fin) = 0;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(SpdyReceiver);
+  };
+
+  // Create a converter that will send frame data to the given receiver.  The
+  // converter does *not* gain ownership of the receiver.
+  HttpToSpdyConverter(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+  ~HttpToSpdyConverter();
+
+  // Parse and process the next chunk of input; return true on success, false
+  // on failure.
+  bool ProcessInput(base::StringPiece input_data);
+  bool ProcessInput(const char* data, size_t size) {
+    return ProcessInput(base::StringPiece(data, size));
+  }
+
+  // Flush out any buffered data.
+  void Flush();
+
+ private:
+  class ConverterImpl;
+  scoped_ptr<ConverterImpl> impl_;
+  HttpResponseParser parser_;
+
+  DISALLOW_COPY_AND_ASSIGN(HttpToSpdyConverter);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_

Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/http_to_spdy_converter_test.cc Thu May  1 11:39:27 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/strings/string_piece.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DeleteArg;
+using testing::Eq;
+using testing::InSequence;
+using testing::Pointee;
+
+namespace {
+
+class MockSpdyReceiver : public mod_spdy::HttpToSpdyConverter::SpdyReceiver {
+ public:
+  MOCK_METHOD2(ReceiveSynReply, void(net::SpdyHeaderBlock* headers,
+                                     bool flag_fin));
+  MOCK_METHOD2(ReceiveData, void(base::StringPiece data, bool flag_fin));
+};
+
+class HttpToSpdyConverterTest :
+      public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+  HttpToSpdyConverterTest() : converter_(GetParam(), &receiver_) {}
+
+ protected:
+  const char* status_header_name() const {
+    return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+            mod_spdy::spdy::kSpdy2Status :
+            mod_spdy::spdy::kSpdy3Status);
+  }
+  const char* version_header_name() const {
+    return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+            mod_spdy::spdy::kSpdy2Version :
+            mod_spdy::spdy::kSpdy3Version);
+  }
+
+  MockSpdyReceiver receiver_;
+  mod_spdy::HttpToSpdyConverter converter_;
+  net::SpdyHeaderBlock expected_headers_;
+};
+
+// Simple response with a small payload.  We should get a SYN_REPLY and a DATA
+// frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithContentLength) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "14";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+  expected_headers_["x-whatever"] = "foobar";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 14\r\n"
+      "Content-Type:      text/plain\r\n"
+      "X-Whatever:foobar\r\n"
+      "\r\n"
+      "Hello, world!\n"
+      "\r\n"));
+}
+
+// The data arrives in two chunks, but they're small, so we should consolidate
+// them into a single DATA frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithChunking) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Connection: Keep-Alive\r\n"
+      "Content-Type: text/plain\r\n"
+      "Keep-Alive: timeout=10, max=5\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "\r\n"
+      "6\r\n"
+      "Hello,\r\n"
+      "8\r\n"
+      " world!\n\r\n"
+      "0\r\n"
+      "\r\n"));
+}
+
+// Test that we don't get tripped up if there is garbage after the end of
+// a chunked message.
+TEST_P(HttpToSpdyConverterTest, ChunkedEncodingWithTrailingGarbage) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Type: text/plain\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "\r\n"
+      "E\r\n"
+      "Hello, world!\n\r\n"
+      "0\r\n"
+      "0\r\n" // multiple last-chunks
+      "\r\n\x1bGaRbAgE"));  // and also some garbage bytes
+}
+
+// No response body, so we should get the FLAG_FIN on the SYN_REPLY, and no
+// DATA frames.
+TEST_P(HttpToSpdyConverterTest, NoResponseBody) {
+  expected_headers_[status_header_name()] = "301";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_["location"] = "https://www.example.com/";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 301 Moved permenantly\r\n"
+      "Location: https://www.example.com/\r\n"
+      "\r\n"));
+}
+
+// Simple response with a large payload.  We should get a SYN_REPLY and
+// multiple DATA frames.
+TEST_P(HttpToSpdyConverterTest, BreakUpLargeDataIntoMultipleFrames) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "10000";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(1808, 'x')), Eq(true)));
+
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 10000\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n" +
+      std::string(10000, 'x') +
+      "\r\n"));
+}
+
+// Test that we buffer data until we get the full frame.
+TEST_P(HttpToSpdyConverterTest, BufferUntilWeHaveACompleteFrame) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "4096";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  // Send some of the headers.  We shouldn't get anything out yet.
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 4096\r\n"));
+  // Send the rest of the headers, and some of the data.  We should get the
+  // SYN_REPLY now, but no data yet.
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  ASSERT_TRUE(converter_.ProcessInput(
+      "Content-Type: text/plain\r\n"
+      "\r\n" +
+      std::string(2000, 'x')));
+  // Send some more data, but still not enough for a full frame.
+  ASSERT_TRUE(converter_.ProcessInput(std::string(2000, 'x')));
+  // Send the last of the data.  We should finally get the one DATA frame.
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(true)));
+  ASSERT_TRUE(converter_.ProcessInput(std::string(96, 'x')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, RespectFlushes) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "4096";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  // Send the headers and some of the data (not enough for a full frame).  We
+  // should get the headers out, but no data yet.
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 4096\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n" +
+      std::string(2000, 'x')));
+  // Perform a flush.  We should get the data sent so far.
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2000, 'x')), Eq(false)));
+  converter_.Flush();
+  // Send the rest of the data.  We should get out a second DATA frame, with
+  // FLAG_FIN set.
+  EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2096, 'y')), Eq(true)));
+  ASSERT_TRUE(converter_.ProcessInput(std::string(2096, 'y')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, FlushAfterEndDoesNothing) {
+  expected_headers_[status_header_name()] = "200";
+  expected_headers_[version_header_name()] = "HTTP/1.1";
+  expected_headers_[mod_spdy::http::kContentLength] = "6";
+  expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+  InSequence seq;
+  EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+                                         Eq(false)));
+  EXPECT_CALL(receiver_, ReceiveData(Eq("foobar"), Eq(true)));
+  ASSERT_TRUE(converter_.ProcessInput(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 6\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n"
+      "foobar"));
+  // Flushing after we're done (even multiple times) should be permitted, but
+  // should do nothing.
+  converter_.Flush();
+  converter_.Flush();
+  converter_.Flush();
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, HttpToSpdyConverterTest, testing::Values(
+    mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+    mod_spdy::spdy::SPDY_VERSION_3_1));
+
+}  // namespace

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

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

Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/protocol_util.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/protocol_util_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/protocol_util_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/protocol_util_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/protocol_util_test.cc Thu May  1 11:39:27 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/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.cc Thu May  1 11:39:27 2014
@@ -0,0 +1,112 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/server_push_discovery_learner.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/strings/string_util.h"
+
+namespace mod_spdy {
+
+namespace {
+
+int32_t GetPriorityFromExtension(const std::string& url) {
+  if (EndsWith(url, ".js", false)) {
+    return 1;
+  } else if (EndsWith(url, ".css", false)) {
+    return 1;
+  } else {
+    return -1;
+  }
+}
+
+}  // namespace
+
+ServerPushDiscoveryLearner::ServerPushDiscoveryLearner() {}
+
+std::vector<ServerPushDiscoveryLearner::Push>
+ServerPushDiscoveryLearner::GetPushes(const std::string& master_url) {
+  base::AutoLock lock(lock_);
+  UrlData& url_data = url_data_[master_url];
+  std::vector<Push> pushes;
+
+  uint64_t threshold = url_data.first_hit_count / 2;
+
+  std::vector<AdjacentData> significant_adjacents;
+
+  for (std::map<std::string, AdjacentData>::const_iterator it =
+           url_data.adjacents.begin(); it != url_data.adjacents.end(); ++it) {
+    if (it->second.hit_count >= threshold)
+      significant_adjacents.push_back(it->second);
+  }
+
+  // Sort by average time from initial request. We want to provide the child
+  // resources that the client needs immediately with a higher priority.
+  std::sort(significant_adjacents.begin(), significant_adjacents.end(),
+            &CompareAdjacentDataByAverageTimeFromInit);
+
+  for (size_t i = 0; i < significant_adjacents.size(); ++i) {
+    const AdjacentData& adjacent = significant_adjacents[i];
+
+    // Give certain URLs fixed high priorities based on their extension.
+    int32_t priority = GetPriorityFromExtension(adjacent.adjacent_url);
+
+    // Otherwise, assign a higher priority based on its average request order.
+    if (priority < 0) {
+      priority = 2 + (i * 6 / significant_adjacents.size());
+    }
+
+    pushes.push_back(Push(adjacent.adjacent_url, priority));
+  }
+
+  return pushes;
+}
+
+void ServerPushDiscoveryLearner::AddFirstHit(const std::string& master_url) {
+  base::AutoLock lock(lock_);
+  UrlData& url_data = url_data_[master_url];
+  ++url_data.first_hit_count;
+}
+
+void ServerPushDiscoveryLearner::AddAdjacentHit(const std::string& master_url,
+                                                const std::string& adjacent_url,
+                                                int64_t time_from_init) {
+  base::AutoLock lock(lock_);
+  std::map<std::string, AdjacentData>& master_url_adjacents =
+      url_data_[master_url].adjacents;
+
+  if (master_url_adjacents.find(adjacent_url) == master_url_adjacents.end()) {
+    master_url_adjacents.insert(
+        make_pair(adjacent_url, AdjacentData(adjacent_url)));
+  }
+
+  AdjacentData& adjacent_data = master_url_adjacents.find(adjacent_url)->second;
+
+  ++adjacent_data.hit_count;
+  double inverse_hit_count = 1.0 / adjacent_data.hit_count;
+
+  adjacent_data.average_time_from_init =
+      inverse_hit_count * time_from_init +
+      (1 - inverse_hit_count) * adjacent_data.average_time_from_init;
+}
+
+// static
+bool ServerPushDiscoveryLearner::CompareAdjacentDataByAverageTimeFromInit(
+    const AdjacentData& a, const AdjacentData& b) {
+  return a.average_time_from_init < b.average_time_from_init;
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.h Thu May  1 11:39:27 2014
@@ -0,0 +1,88 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_
+#define MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+// Used to keep track of request patterns and generate X-Associated-Content.
+// Stores the initial |master_url| request and the subsequent |adjacent_url|s.
+// Generates reasonable pushes based on a simple heuristic.
+class ServerPushDiscoveryLearner {
+ public:
+  struct Push {
+    Push(const std::string& adjacent_url, net::SpdyPriority priority)
+        : adjacent_url(adjacent_url),
+          priority(priority) {
+    }
+
+    std::string adjacent_url;
+    net::SpdyPriority priority;
+  };
+
+  ServerPushDiscoveryLearner();
+
+  // Gets a list of child resource pushes for a given |master_url|.
+  std::vector<Push> GetPushes(const std::string& master_url);
+
+  // Called when module receives an initial master request for a page, and
+  // module intends to log future child resource requests for learning.
+  void AddFirstHit(const std::string& master_url);
+
+  // Called when module receives child resource requests associated with
+  // a master request received earlier. |time_from_init| is the time in
+  // microseconds between this adjacent hit on |adjacent_url| and the initial
+  // hit on the |master_url|.
+  void AddAdjacentHit(const std::string& master_url,
+                      const std::string& adjacent_url, int64_t time_from_init);
+
+ private:
+  struct AdjacentData {
+    AdjacentData(const std::string& adjacent_url)
+        : adjacent_url(adjacent_url),
+          hit_count(0),
+          average_time_from_init(0) {
+    }
+
+    std::string adjacent_url;
+    uint64_t hit_count;
+    int64_t average_time_from_init;
+  };
+
+  struct UrlData {
+    UrlData() : first_hit_count(0) {}
+
+    uint64_t first_hit_count;
+    std::map<std::string, AdjacentData> adjacents;
+  };
+
+  static bool CompareAdjacentDataByAverageTimeFromInit(const AdjacentData& a,
+                                                       const AdjacentData& b);
+
+  std::map<std::string, UrlData> url_data_;
+  base::Lock lock_;
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_LEARNER_H_

Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_learner_test.cc Thu May  1 11:39:27 2014
@@ -0,0 +1,101 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/server_push_discovery_learner.h"
+
+#include "gtest/gtest.h"
+
+namespace mod_spdy {
+
+TEST(ServerPushDiscoveryLearnerTest, TrivialNoPush) {
+  ServerPushDiscoveryLearner learner;
+
+  EXPECT_TRUE(learner.GetPushes("a").empty());
+
+  learner.AddFirstHit("a");
+  learner.AddFirstHit("a");
+  learner.AddFirstHit("a");
+  learner.AddFirstHit("a");
+
+  EXPECT_TRUE(learner.GetPushes("a").empty());
+
+  // Add an adjacent hit, but it should not be enough to generate a push.
+  learner.AddAdjacentHit("a", "b", 0);
+
+  EXPECT_TRUE(learner.GetPushes("a").empty());
+}
+
+TEST(ServerPushDiscoveryLearnerTest, TrivialYesPush) {
+  ServerPushDiscoveryLearner learner;
+
+  learner.AddFirstHit("a");
+  learner.AddAdjacentHit("a", "b", 0);
+
+  std::vector<ServerPushDiscoveryLearner::Push> pushes = learner.GetPushes("a");
+  EXPECT_FALSE(pushes.empty());
+  EXPECT_EQ("b", pushes.front().adjacent_url);
+}
+
+TEST(ServerPushDiscoveryLearnerTest, PushOrder) {
+  ServerPushDiscoveryLearner learner;
+
+  learner.AddFirstHit("a");
+  learner.AddAdjacentHit("a", "b", 1);
+  learner.AddAdjacentHit("a", "c", 2);
+
+  learner.AddFirstHit("a");
+  learner.AddAdjacentHit("a", "b", 2);
+  learner.AddAdjacentHit("a", "c", 3);
+
+  learner.AddFirstHit("a");
+  learner.AddAdjacentHit("a", "b", 3);
+  learner.AddAdjacentHit("a", "c", 4);
+
+  std::vector<ServerPushDiscoveryLearner::Push> pushes = learner.GetPushes("a");
+  EXPECT_EQ(2u, pushes.size());
+  EXPECT_EQ("b", pushes.front().adjacent_url);
+  EXPECT_EQ("c", pushes.back().adjacent_url);
+}
+
+TEST(ServerPushDiscoveryLearnerTest, TurnoverPoint) {
+  ServerPushDiscoveryLearner learner;
+
+  uint64_t a_requests = 0;
+  uint64_t b_requests = 0;
+
+  // Put in 20 initial requests with no child requests.
+  for (int i = 0; i < 20; ++i) {
+    learner.AddFirstHit("a");
+    ++a_requests;
+  }
+
+  // Put in more b requests until it tips over
+  for (int i = 0; i < 50; ++i) {
+    learner.AddAdjacentHit("a", "b", 0);
+    ++b_requests;
+    std::vector<ServerPushDiscoveryLearner::Push> pushes =
+        learner.GetPushes("a");
+
+    if (b_requests >= (a_requests / 2)) {
+      ASSERT_TRUE(pushes.size() == 1) << "(a, b) = " << a_requests << ","
+                                      << b_requests;
+      EXPECT_EQ("b", pushes.front().adjacent_url);
+    } else {
+      EXPECT_TRUE(pushes.empty()) << "(a, b) = " << a_requests << ","
+                                  << b_requests;
+    }
+  }
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.cc Thu May  1 11:39:27 2014
@@ -0,0 +1,81 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/server_push_discovery_session.h"
+
+namespace mod_spdy {
+
+const int64_t kServerPushSessionTimeout = 2000000;  // 2 second in microseconds.
+
+ServerPushDiscoverySessionPool::ServerPushDiscoverySessionPool()
+    : next_session_id_(0) {
+}
+
+ServerPushDiscoverySession* ServerPushDiscoverySessionPool::GetExistingSession(
+    SessionId session_id,
+    int64_t request_time) {
+  base::AutoLock lock(lock_);
+  std::map<SessionId, ServerPushDiscoverySession>::iterator it =
+      session_cache_.find(session_id);
+  if (it == session_cache_.end() ||
+      it->second.TimeFromLastAccess(request_time) > kServerPushSessionTimeout) {
+    return NULL;
+  }
+
+  return &(it->second);
+}
+
+ServerPushDiscoverySessionPool::SessionId
+ServerPushDiscoverySessionPool::CreateSession(
+    int64_t request_time,
+    const std::string& request_url,
+    bool took_push) {
+  base::AutoLock lock(lock_);
+  CleanExpired(request_time);
+  // Create a session to track this request chain
+  SessionId session_id = next_session_id_++;
+  session_cache_.insert(
+      std::make_pair(session_id,
+                     ServerPushDiscoverySession(
+                         session_id, request_time, request_url, took_push)));
+  return session_id;
+}
+
+void ServerPushDiscoverySessionPool::CleanExpired(int64_t request_time) {
+  lock_.AssertAcquired();
+
+  std::map<int64_t, ServerPushDiscoverySession>::iterator it =
+      session_cache_.begin();
+  while (it != session_cache_.end()) {
+    if (it->second.TimeFromLastAccess(request_time) >
+            kServerPushSessionTimeout) {
+      session_cache_.erase(it++);
+    } else {
+      ++it;
+    }
+  }
+}
+
+ServerPushDiscoverySession::ServerPushDiscoverySession(
+    ServerPushDiscoverySessionPool::SessionId session_id,
+    int64_t initial_request_time,
+    const std::string& master_url,
+    bool took_push)
+    : session_id_(session_id),
+      initial_request_time_(initial_request_time),
+      master_url_(master_url),
+      took_push_(took_push),
+      last_access_(initial_request_time) {}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.h Thu May  1 11:39:27 2014
@@ -0,0 +1,103 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_SESSION_H_
+#define MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_SESSION_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+
+namespace mod_spdy {
+
+class ServerPushDiscoverySession;
+
+extern const int64_t kServerPushSessionTimeout;
+
+// This class manages a pool of sessions used to discover X-Associated-Content.
+// It tracks an initial request, i.e., for  'index.html', and all its child
+// requests, i.e., 'logo.gif', 'style.css'. This should be created during
+// per-process initialization. Class may be called from multiple threads.
+class ServerPushDiscoverySessionPool {
+ public:
+  typedef int64_t SessionId;
+
+  ServerPushDiscoverySessionPool();
+
+  // Retrieves an existing session. Returns NULL if it's been timed out already.
+  // In which case the module should create a new session. The returned pointer
+  // is an object owned by the pool and must not be deleted by the caller.
+  ServerPushDiscoverySession* GetExistingSession(SessionId session_id,
+                                                 int64_t request_time);
+
+  // Creates a new session. |took_push| denotes if the the initial request
+  // response included an auto-learned X-Associated-Content header, since we
+  // don't want to self-reinforce our URL statistics. Returns the session_id
+  // for user agent storage.
+  int64_t CreateSession(int64_t request_time,
+                        const std::string& request_url,
+                        bool took_push);
+
+ private:
+  // Caller should be holding |lock_|.
+  void CleanExpired(int64_t request_time);
+
+  SessionId next_session_id_;
+  std::map<SessionId, ServerPushDiscoverySession> session_cache_;
+
+  base::Lock lock_;
+};
+
+// Represents an initial page request and all its child resource requests.
+class ServerPushDiscoverySession {
+ public:
+  // Update the last access time on this session, extending its lifetime.
+  void UpdateLastAccessTime(int64_t now) { last_access_ = now; }
+
+  // Returns the elapsed microseconds between the initial request and this one.
+  int64_t TimeFromInit(int64_t request_time) const {
+    return request_time - initial_request_time_;
+  }
+
+  // Returns the elapsed microseconds between the last request on this session
+  // and the current request.
+  int64_t TimeFromLastAccess(int64_t request_time) const {
+    return request_time - last_access_;
+  }
+
+  const std::string& master_url() const { return master_url_; }
+  bool took_push() const { return took_push_; }
+
+ private:
+  friend class ServerPushDiscoverySessionPool;
+
+  ServerPushDiscoverySession(
+      ServerPushDiscoverySessionPool::SessionId session_id,
+      int64_t initial_request_time,
+      const std::string& master_url,
+      bool took_push);
+
+  ServerPushDiscoverySessionPool::SessionId session_id_;
+  int64_t initial_request_time_;
+  std::string master_url_;
+  int64_t took_push_;
+
+  int64_t last_access_;
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SERVER_PUSH_DISCOVERY_SESSION_H_

Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/server_push_discovery_session_test.cc Thu May  1 11:39:27 2014
@@ -0,0 +1,56 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/server_push_discovery_session.h"
+
+#include "gtest/gtest.h"
+
+namespace mod_spdy {
+
+TEST(ServerPushDiscoverySessionTest, NoSession) {
+  ServerPushDiscoverySessionPool pool;
+  EXPECT_EQ(NULL, pool.GetExistingSession(0, 0));
+}
+
+TEST(ServerPushDiscoverySessionTest, GetSession) {
+  ServerPushDiscoverySessionPool pool;
+  std::vector<int64_t> session_ids;
+  for (int i = 0; i < 40; i++)
+    session_ids.push_back(pool.CreateSession(0, "", false));
+
+  for (int i = 0; i < 40; i++)
+    EXPECT_TRUE(pool.GetExistingSession(session_ids[i], 0));
+}
+
+TEST(ServerPushDiscoverySessionTest, ExpiryTest) {
+  ServerPushDiscoverySessionPool pool;
+  std::vector<int64_t> session_ids;
+  for (int i = 0; i < 20; i++) {
+    session_ids.push_back(pool.CreateSession(0, "", false));
+  }
+
+  for (int i = 0; i < 20; i++) {
+    int64_t time = i * kServerPushSessionTimeout / 10;
+    bool expired = time > kServerPushSessionTimeout;
+    session_ids.push_back(pool.CreateSession(0, "", false));
+
+    if (expired) {
+      EXPECT_FALSE(pool.GetExistingSession(session_ids[i], time));
+    } else {
+      EXPECT_TRUE(pool.GetExistingSession(session_ids[i], time));
+    }
+  }
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.cc Thu May  1 11:39:27 2014
@@ -0,0 +1,161 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/shared_flow_control_window.h"
+
+#include "base/logging.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SharedFlowControlWindow::SharedFlowControlWindow(
+    int32 initial_input_window_size, int32 initial_output_window_size)
+    : condvar_(&lock_),
+      aborted_(false),
+      init_input_window_size_(initial_input_window_size),
+      input_window_size_(initial_input_window_size),
+      input_bytes_consumed_(0),
+      output_window_size_(initial_output_window_size) {}
+
+SharedFlowControlWindow::~SharedFlowControlWindow() {}
+
+void SharedFlowControlWindow::Abort() {
+  base::AutoLock autolock(lock_);
+  aborted_ = true;
+  condvar_.Broadcast();
+}
+
+bool SharedFlowControlWindow::is_aborted() const {
+  base::AutoLock autolock(lock_);
+  return aborted_;
+}
+
+int32 SharedFlowControlWindow::current_input_window_size() const {
+  base::AutoLock autolock(lock_);
+  return input_window_size_;
+}
+
+int32 SharedFlowControlWindow::current_output_window_size() const {
+  base::AutoLock autolock(lock_);
+  return output_window_size_;
+}
+
+int32 SharedFlowControlWindow::input_bytes_consumed() const {
+  base::AutoLock autolock(lock_);
+  return input_bytes_consumed_;
+}
+
+bool SharedFlowControlWindow::OnReceiveInputData(size_t length) {
+  base::AutoLock autolock(lock_);
+  if (aborted_) {
+    return true;
+  }
+  DCHECK_GE(input_window_size_, 0);
+  if (static_cast<size_t>(input_window_size_) < length) {
+    return false;
+  }
+  input_window_size_ -= length;
+  return true;
+}
+
+int32 SharedFlowControlWindow::OnInputDataConsumed(size_t length) {
+  base::AutoLock autolock(lock_);
+  if (aborted_) {
+    return 0;
+  }
+
+  DCHECK_GE(input_bytes_consumed_, 0);
+
+  // Check for overflow; this should never happen unless there is a bug in
+  // mod_spdy, since we should never say we've consumed more data than we've
+  // actually received.
+  {
+    const int64 new_input_bytes_consumed =
+        static_cast<int64>(input_bytes_consumed_) + static_cast<int64>(length);
+    CHECK_LE(new_input_bytes_consumed,
+             static_cast<int64>(init_input_window_size_));
+    CHECK_LE(new_input_bytes_consumed + static_cast<int64>(input_window_size_),
+             static_cast<int64>(init_input_window_size_));
+    input_bytes_consumed_ = new_input_bytes_consumed;
+  }
+
+  // Only send a WINDOW_UPDATE when we've consumed 1/16 of the maximum shared
+  // window size, so that we don't send lots of small WINDOW_UDPATE frames.
+  if (input_bytes_consumed_ < init_input_window_size_ / 16) {
+    return 0;
+  } else {
+    input_window_size_ += input_bytes_consumed_;
+    const int32 consumed = input_bytes_consumed_;
+    input_bytes_consumed_ = 0;
+    return consumed;
+  }
+}
+
+void SharedFlowControlWindow::OnInputDataConsumedSendUpdateIfNeeded(
+    size_t length, SpdyFramePriorityQueue* output_queue) {
+  const int32 update = OnInputDataConsumed(length);
+  if (update > 0) {
+    output_queue->Insert(SpdyFramePriorityQueue::kTopPriority,
+                         new net::SpdyWindowUpdateIR(0, update));
+  }
+}
+
+int32 SharedFlowControlWindow::RequestOutputQuota(int32 amount_requested) {
+  base::AutoLock autolock(lock_);
+  DCHECK_GT(amount_requested, 0);
+
+  while (!aborted_ && output_window_size_ <= 0) {
+    condvar_.Wait();
+  }
+
+  if (aborted_) {
+    return 0;
+  }
+
+  // Give as much output quota as we can, but not more than is asked for.
+  DCHECK_GT(output_window_size_, 0);
+  const int32 amount_to_give = std::min(amount_requested, output_window_size_);
+  output_window_size_ -= amount_to_give;
+  DCHECK_GE(output_window_size_, 0);
+  return amount_to_give;
+}
+
+bool SharedFlowControlWindow::IncreaseOutputWindowSize(int32 delta) {
+  base::AutoLock autolock(lock_);
+  DCHECK_GE(delta, 0);
+  if (aborted_) {
+    return true;
+  }
+
+  // Check for overflow; this can happen if the client is misbehaving.
+  const int64 new_size =
+      static_cast<int64>(output_window_size_) + static_cast<int64>(delta);
+  if (new_size > static_cast<int64>(net::kSpdyMaximumWindowSize)) {
+    return false;
+  }
+
+  // Increase the shared output window size, and wake up any stream threads
+  // that are waiting for output quota.
+  output_window_size_ += delta;
+  DCHECK_LE(output_window_size_, net::kSpdyMaximumWindowSize);
+  if (output_window_size_ > 0) {
+    condvar_.Broadcast();
+  }
+  return true;
+}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.h Thu May  1 11:39:27 2014
@@ -0,0 +1,107 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SHARED_FLOW_CONTROL_WINDOW_H_
+#define MOD_SPDY_COMMON_SHARED_FLOW_CONTROL_WINDOW_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace mod_spdy {
+
+class SpdyFramePriorityQueue;
+
+// SPDY/3.1 introduces an additional session-wide flow control window shared by
+// all streams, and represented in WINDOW_UPDATE frames as "stream 0".  The
+// SharedFlowControlWindow class is a thread-safe object for tracking the size
+// of this shared flow control window and enforcing flow-control rules, in both
+// the input and output directions.
+class SharedFlowControlWindow {
+ public:
+  SharedFlowControlWindow(int32 initial_input_window_size,
+                          int32 initial_output_window_size);
+  ~SharedFlowControlWindow();
+
+  // Wake up all threads blocked on other methods.  Future method calls to this
+  // class will return immediately with no effect.
+  void Abort();
+
+  // Return true if Abort() has been called.
+  bool is_aborted() const;
+
+  // Get the current input/ouput window sizes (of course, it might very soon
+  // change if other threads are accessing this object).  This is primarily
+  // useful for testing/debugging.
+  int32 current_input_window_size() const;
+  int32 current_output_window_size() const;
+  // How many input bytes have been consumed that _haven't_ yet been
+  // acknowledged by a WINDOW_UPDATE (signaled by OnInputDataConsumed)?  This
+  // is primarily useful for testing/debugging.
+  int32 input_bytes_consumed() const;
+
+  // Called by the connection thread when input data is received from the
+  // client.  Returns true (and reduces the input window size) on success, or
+  // false if the input window is too small to accept that much data, in which
+  // case the client has committed a flow control error and should be sent a
+  // GOAWAY.  If the SharedFlowControlWindow has already been aborted
+  // (i.e. because the session is shutting down), returns true with no effect.
+  bool OnReceiveInputData(size_t length) WARN_UNUSED_RESULT;
+
+  // Called by stream threads when input data from the client has been
+  // consumed.  If a session WINDOW_UPDATE (stream 0) should be sent, returns
+  // the size of the update to send; otherwise, returns zero.  If the
+  // SharedFlowControlWindow has already been aborted, returns 0 with no
+  // effect.
+  int32 OnInputDataConsumed(size_t length) WARN_UNUSED_RESULT;
+
+  // Like OnInputDataConsumed, but automatically send a WINDOW_UPDATE for
+  // stream 0 if needed.
+  void OnInputDataConsumedSendUpdateIfNeeded(
+      size_t length, SpdyFramePriorityQueue* output_queue);
+
+  // This should be called by stream threads to consume quota from the shared
+  // flow control window.  Consumes up to `amount_requested` bytes from the
+  // window (less if the window is currently smaller than `amount_requested`)
+  // and returns the number of bytes successfully consumed.  If the window is
+  // currently empty, blocks until some value can be returned (or the
+  // SharedFlowControlWindow is aborted); if the SharedFlowControlWindow is
+  // aborted, returns zero.  The `amount_requested` must be strictly positive.
+  int32 RequestOutputQuota(int32 amount_requested) WARN_UNUSED_RESULT;
+
+  // This should be called by the connection thread to adjust the window size,
+  // due to receiving a WINDOW_UPDATE frame from the client.  The delta
+  // argument must be non-negative (WINDOW_UPDATE is never negative).  Return
+  // false if the delta would cause the window size to overflow (in which case
+  // the client has committed a flow control error and should be sent a
+  // GOAWAY), true otherwise.  If the SharedFlowControlWindow has already been
+  // aborted, returns true with no effect.
+  bool IncreaseOutputWindowSize(int32 delta) WARN_UNUSED_RESULT;
+
+ private:
+  mutable base::Lock lock_;  // protects the below fields
+  base::ConditionVariable condvar_;
+  bool aborted_;
+  const int32 init_input_window_size_;
+  int32 input_window_size_;
+  int32 input_bytes_consumed_;
+  int32 output_window_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedFlowControlWindow);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SHARED_FLOW_CONTROL_WINDOW_H_

Propchange: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/shared_flow_control_window_test.cc Thu May  1 11:39:27 2014
@@ -0,0 +1,216 @@
+// Copyright 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/shared_flow_control_window.h"
+
+#include "base/threading/platform_thread.h"
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/testing/spdy_frame_matchers.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Test that when we receive input data, the input window size decreases, and
+// if we try to receive more data than the window allows, then
+// OnReceiveInputData returns false.
+TEST(SharedFlowControlWindowTest, ReceiveInput) {
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+  ASSERT_EQ(1000, shared_window.current_input_window_size());
+
+  EXPECT_TRUE(shared_window.OnReceiveInputData(320));
+  ASSERT_EQ(680, shared_window.current_input_window_size());
+
+  EXPECT_TRUE(shared_window.OnReceiveInputData(600));
+  ASSERT_EQ(80, shared_window.current_input_window_size());
+
+  EXPECT_FALSE(shared_window.OnReceiveInputData(100));
+  ASSERT_EQ(80, shared_window.current_input_window_size());
+
+  EXPECT_TRUE(shared_window.OnReceiveInputData(80));
+  ASSERT_EQ(0, shared_window.current_input_window_size());
+
+  EXPECT_FALSE(shared_window.OnReceiveInputData(1));
+  ASSERT_EQ(0, shared_window.current_input_window_size());
+}
+
+// Test that when we consume input data that we've already received, the input
+// window size goes up, but only once we've consumed enough total data for it
+// to be worth it to send a WINDOW_UPDATE.
+TEST(SharedFlowControlWindowTest, ConsumeInput) {
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+  ASSERT_FALSE(shared_window.is_aborted());
+  ASSERT_EQ(1000, shared_window.current_input_window_size());
+
+  EXPECT_TRUE(shared_window.OnReceiveInputData(1000));
+  EXPECT_EQ(0, shared_window.current_input_window_size());
+
+  EXPECT_EQ(0, shared_window.OnInputDataConsumed(10));
+  EXPECT_EQ(0, shared_window.current_input_window_size());
+  EXPECT_EQ(10, shared_window.input_bytes_consumed());
+
+  EXPECT_EQ(0, shared_window.OnInputDataConsumed(40));
+  EXPECT_EQ(0, shared_window.current_input_window_size());
+  EXPECT_EQ(50, shared_window.input_bytes_consumed());
+
+  EXPECT_EQ(550, shared_window.OnInputDataConsumed(500));
+  EXPECT_EQ(550, shared_window.current_input_window_size());
+  EXPECT_EQ(0, shared_window.input_bytes_consumed());
+
+  EXPECT_EQ(0, shared_window.OnInputDataConsumed(10));
+  EXPECT_EQ(550, shared_window.current_input_window_size());
+  EXPECT_EQ(10, shared_window.input_bytes_consumed());
+
+  EXPECT_EQ(450, shared_window.OnInputDataConsumed(440));
+  EXPECT_EQ(1000, shared_window.current_input_window_size());
+  EXPECT_EQ(0, shared_window.input_bytes_consumed());
+
+  shared_window.Abort();
+  ASSERT_TRUE(shared_window.is_aborted());
+
+  EXPECT_TRUE(shared_window.OnReceiveInputData(1));
+  EXPECT_EQ(0, shared_window.OnInputDataConsumed(1000));
+}
+
+// Test that OnInputDataConsumedSendUpdateIfNeeded sends WINDOW_UPDATE frames
+// correctly.
+TEST(SharedFlowControlWindowTest, ConsumeInputSendUpdate) {
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+  ASSERT_EQ(1000, shared_window.current_input_window_size());
+
+  EXPECT_TRUE(shared_window.OnReceiveInputData(1000));
+  EXPECT_EQ(0, shared_window.current_input_window_size());
+
+  mod_spdy::SpdyFramePriorityQueue queue;
+  net::SpdyFrameIR* raw_frame;
+
+  shared_window.OnInputDataConsumedSendUpdateIfNeeded(5, &queue);
+  EXPECT_EQ(0, shared_window.current_input_window_size());
+  EXPECT_EQ(5, shared_window.input_bytes_consumed());
+  ASSERT_FALSE(queue.Pop(&raw_frame));
+
+  shared_window.OnInputDataConsumedSendUpdateIfNeeded(933, &queue);
+  EXPECT_EQ(938, shared_window.current_input_window_size());
+  EXPECT_EQ(0, shared_window.input_bytes_consumed());
+  ASSERT_TRUE(queue.Pop(&raw_frame));
+  scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+  EXPECT_THAT(*frame, mod_spdy::testing::IsWindowUpdate(0, 938));
+  ASSERT_FALSE(queue.Pop(&raw_frame));
+}
+
+// Test basic usage of RequestOutputQuota and IncreaseOutputWindowSize.
+TEST(SharedFlowControlWindowTest, OutputBasic) {
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 1000);
+  EXPECT_FALSE(shared_window.is_aborted());
+  EXPECT_EQ(1000, shared_window.current_output_window_size());
+
+  EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(0));
+  EXPECT_EQ(1000, shared_window.current_output_window_size());
+
+  EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(47));
+  EXPECT_EQ(1047, shared_window.current_output_window_size());
+
+  EXPECT_EQ(800, shared_window.RequestOutputQuota(800));
+  EXPECT_EQ(247, shared_window.current_output_window_size());
+
+  EXPECT_EQ(247, shared_window.RequestOutputQuota(800));
+  EXPECT_EQ(0, shared_window.current_output_window_size());
+
+  EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(2000));
+  EXPECT_EQ(2000, shared_window.current_output_window_size());
+
+  // After aborting, RequestOutputQuota always returns zero (without blocking).
+  EXPECT_FALSE(shared_window.is_aborted());
+  shared_window.Abort();
+  EXPECT_TRUE(shared_window.is_aborted());
+  EXPECT_EQ(0, shared_window.RequestOutputQuota(800));
+  EXPECT_EQ(0, shared_window.RequestOutputQuota(9999));
+}
+
+// When run, a RequestOutputQuotaTask requests quota from the given
+// SharedFlowControlWindow.
+class RequestOutputQuotaTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+  RequestOutputQuotaTask(mod_spdy::SharedFlowControlWindow* window,
+                         int32 request)
+      : window_(window), request_(request), received_(-1) {}
+  virtual void Run() {
+    received_ = window_->RequestOutputQuota(request_);
+  }
+  int32 received() const { return received_; }
+ private:
+  mod_spdy::SharedFlowControlWindow* const window_;
+  const int32 request_;
+  int32 received_;
+  DISALLOW_COPY_AND_ASSIGN(RequestOutputQuotaTask);
+};
+
+// Test that RequestOutputQuota blocks if the window is completely empty.
+TEST(SharedFlowControlWindowTest, OutputBlocking) {
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 350);
+
+  EXPECT_EQ(200, shared_window.RequestOutputQuota(200));
+  EXPECT_EQ(150, shared_window.current_output_window_size());
+
+  EXPECT_EQ(150, shared_window.RequestOutputQuota(200));
+  EXPECT_EQ(0, shared_window.current_output_window_size());
+
+  // Start an async task to request 200 bytes.  It should block, because the
+  // window is empty.
+  RequestOutputQuotaTask* task =
+      new RequestOutputQuotaTask(&shared_window, 200);
+  mod_spdy::testing::AsyncTaskRunner runner(task);
+  ASSERT_TRUE(runner.Start());
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+  runner.notification()->ExpectNotSet();
+
+  // Now increase the window size.  RequestOutputQuota should unblock and return
+  // what's available.
+  EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(63));
+  runner.notification()->ExpectSetWithinMillis(100);
+  EXPECT_EQ(63, task->received());
+}
+
+// Test that RequestOutputQuota unblocks if we abort.
+TEST(SharedFlowControlWindowTest, OutputAborting) {
+  mod_spdy::SharedFlowControlWindow shared_window(1000, 350);
+
+  EXPECT_EQ(350, shared_window.RequestOutputQuota(500));
+  EXPECT_EQ(0, shared_window.current_output_window_size());
+
+  // Start an async task to request 200 bytes.  It should block, because the
+  // window is empty.
+  RequestOutputQuotaTask* task =
+      new RequestOutputQuotaTask(&shared_window, 200);
+  mod_spdy::testing::AsyncTaskRunner runner(task);
+  ASSERT_TRUE(runner.Start());
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+  runner.notification()->ExpectNotSet();
+
+  // Now abort.  RequestOutputQuota should unblock and return zero.
+  EXPECT_FALSE(shared_window.is_aborted());
+  shared_window.Abort();
+  runner.notification()->ExpectSetWithinMillis(100);
+  EXPECT_EQ(0, task->received());
+  EXPECT_TRUE(shared_window.is_aborted());
+
+  // Abort again, to check that it's idempotent.
+  shared_window.Abort();
+  EXPECT_TRUE(shared_window.is_aborted());
+  EXPECT_EQ(0, shared_window.RequestOutputQuota(800));
+}
+
+}  // namespace