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