You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2014/05/01 13:43:45 UTC
svn commit: r1591622 [10/33] - in /httpd/mod_spdy/trunk: ./ base/
base/base.xcodeproj/ base/metrics/ build/ build/all.xcodeproj/
build/build_util.xcodeproj/ build/install.xcodeproj/ build/internal/
build/linux/ build/mac/ build/util/ build/win/ install...
Added: httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,122 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/http_string_builder.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/string_piece.h"
+#include "base/stringprintf.h"
+
+namespace {
+
+void OnHeader(const base::StringPiece& key,
+ const base::StringPiece& value,
+ std::string* output) {
+ key.AppendToString(output);
+ output->append(": ");
+ value.AppendToString(output);
+ output->append("\r\n");
+}
+
+} // namespace
+
+namespace mod_spdy {
+
+HttpStringBuilder::HttpStringBuilder(std::string* str)
+ : string_(str), state_(REQUEST_LINE) {
+ CHECK(string_);
+}
+
+HttpStringBuilder::~HttpStringBuilder() {}
+
+void HttpStringBuilder::OnRequestLine(const base::StringPiece& method,
+ const base::StringPiece& path,
+ const base::StringPiece& version) {
+ DCHECK(state_ == REQUEST_LINE);
+ state_ = LEADING_HEADERS;
+ method.AppendToString(string_);
+ string_->push_back(' ');
+ path.AppendToString(string_);
+ string_->push_back(' ');
+ version.AppendToString(string_);
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ DCHECK(state_ == LEADING_HEADERS);
+ OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnLeadingHeadersComplete() {
+ DCHECK(state_ == LEADING_HEADERS);
+ state_ = LEADING_HEADERS_COMPLETE;
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnRawData(const base::StringPiece& data) {
+ DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == RAW_DATA);
+ state_ = RAW_DATA;
+ data.AppendToString(string_);
+}
+
+void HttpStringBuilder::OnDataChunk(const base::StringPiece& data) {
+ DCHECK(state_ == LEADING_HEADERS_COMPLETE || state_ == DATA_CHUNKS);
+ state_ = DATA_CHUNKS;
+ // Encode the data as an HTTP data chunk. See RFC 2616 section 3.6.1 for
+ // details.
+ base::StringAppendF(string_, "%lX\r\n",
+ static_cast<unsigned long>(data.size()));
+ data.AppendToString(string_);
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnDataChunksComplete() {
+ DCHECK(state_ == DATA_CHUNKS);
+ state_ = DATA_CHUNKS_COMPLETE;
+ // Indicate that there are no more HTTP data chunks coming. See RFC 2616
+ // section 3.6.1 for details.
+ string_->append("0\r\n");
+}
+
+void HttpStringBuilder::OnTrailingHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ DCHECK(state_ == DATA_CHUNKS_COMPLETE || state_ == TRAILING_HEADERS);
+ state_ = TRAILING_HEADERS;
+ OnHeader(key, value, string_);
+}
+
+void HttpStringBuilder::OnTrailingHeadersComplete() {
+ DCHECK(state_ == TRAILING_HEADERS);
+ state_ = TRAILING_HEADERS_COMPLETE;
+ string_->append("\r\n");
+}
+
+void HttpStringBuilder::OnComplete() {
+ DCHECK(state_ == LEADING_HEADERS_COMPLETE ||
+ state_ == RAW_DATA ||
+ state_ == DATA_CHUNKS_COMPLETE ||
+ state_ == TRAILING_HEADERS_COMPLETE);
+ if (state_ == DATA_CHUNKS_COMPLETE) {
+ // In this case, there have been data chunks, but we haven't called
+ // OnTrailingHeadersComplete because there were no trailing headers. We
+ // still need an empty line to indicate the end of the request.
+ string_->append("\r\n");
+ }
+ state_ = COMPLETE;
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h Thu May 1 11:43:36 2014
@@ -0,0 +1,69 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
+#define MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+
+namespace mod_spdy {
+
+// An HttpRequestVisitorInterface class that appends to a std::string.
+class HttpStringBuilder : public HttpRequestVisitorInterface {
+ public:
+ explicit HttpStringBuilder(std::string* str);
+ virtual ~HttpStringBuilder();
+
+ bool is_complete() const { return state_ == COMPLETE; }
+
+ // HttpRequestVisitorInterface methods:
+ virtual void OnRequestLine(const base::StringPiece& method,
+ const base::StringPiece& path,
+ const base::StringPiece& version);
+ virtual void OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+ virtual void OnLeadingHeadersComplete();
+ virtual void OnRawData(const base::StringPiece& data);
+ virtual void OnDataChunk(const base::StringPiece& data);
+ virtual void OnDataChunksComplete();
+ virtual void OnTrailingHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+ virtual void OnTrailingHeadersComplete();
+ virtual void OnComplete();
+
+ private:
+ enum State {
+ REQUEST_LINE,
+ LEADING_HEADERS,
+ LEADING_HEADERS_COMPLETE,
+ RAW_DATA,
+ DATA_CHUNKS,
+ DATA_CHUNKS_COMPLETE,
+ TRAILING_HEADERS,
+ TRAILING_HEADERS_COMPLETE,
+ COMPLETE
+ };
+
+ std::string* const string_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpStringBuilder);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_STRING_BUILDER_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/http_string_builder.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,192 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_piece.h"
+#include "mod_spdy/common/http_response_visitor_interface.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// This is the number of bytes we want to send per data frame. We never send
+// data frames larger than this, but we might send smaller ones if we have to
+// flush early.
+// TODO The SPDY folks say that smallish (~4kB) data frames are good; however,
+// we should experiment later on to see what value here performs the best.
+const size_t kTargetDataFrameBytes = 4096;
+
+} // namespace
+
+namespace mod_spdy {
+
+class HttpToSpdyConverter::ConverterImpl : public HttpResponseVisitorInterface{
+ public:
+ ConverterImpl(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+ virtual ~ConverterImpl();
+
+ void Flush();
+
+ // HttpResponseVisitorInterface methods:
+ virtual void OnStatusLine(const base::StringPiece& version,
+ const base::StringPiece& status_code,
+ const base::StringPiece& status_phrase);
+ virtual void OnLeadingHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+ virtual void OnLeadingHeadersComplete(bool fin);
+ virtual void OnData(const base::StringPiece& data, bool fin);
+
+ private:
+ void SendDataIfNecessary(bool flush, bool fin);
+ void SendDataFrame(const char* data, size_t size, bool flag_fin);
+
+ const spdy::SpdyVersion spdy_version_;
+ SpdyReceiver* const receiver_;
+ net::SpdyHeaderBlock headers_;
+ std::string data_buffer_;
+ bool sent_flag_fin_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConverterImpl);
+};
+
+HttpToSpdyConverter::SpdyReceiver::SpdyReceiver() {}
+
+HttpToSpdyConverter::SpdyReceiver::~SpdyReceiver() {}
+
+HttpToSpdyConverter::HttpToSpdyConverter(spdy::SpdyVersion spdy_version,
+ SpdyReceiver* receiver)
+ : impl_(new ConverterImpl(spdy_version, receiver)),
+ parser_(impl_.get()) {}
+
+HttpToSpdyConverter::~HttpToSpdyConverter() {}
+
+bool HttpToSpdyConverter::ProcessInput(base::StringPiece input_data) {
+ return parser_.ProcessInput(input_data);
+}
+
+void HttpToSpdyConverter::Flush() {
+ impl_->Flush();
+}
+
+HttpToSpdyConverter::ConverterImpl::ConverterImpl(
+ spdy::SpdyVersion spdy_version, SpdyReceiver* receiver)
+ : spdy_version_(spdy_version),
+ receiver_(receiver),
+ sent_flag_fin_(false) {
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ CHECK(receiver_);
+}
+
+HttpToSpdyConverter::ConverterImpl::~ConverterImpl() {}
+
+void HttpToSpdyConverter::ConverterImpl::Flush() {
+ SendDataIfNecessary(true, // true = do flush
+ false); // false = not fin yet
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnStatusLine(
+ const base::StringPiece& version,
+ const base::StringPiece& status_code,
+ const base::StringPiece& status_phrase) {
+ DCHECK(headers_.empty());
+ const bool spdy2 = spdy_version_ < spdy::SPDY_VERSION_3;
+ headers_[spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version] =
+ version.as_string();
+ headers_[spdy2 ? spdy::kSpdy2Status : spdy::kSpdy3Status] =
+ status_code.as_string();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeader(
+ const base::StringPiece& key,
+ const base::StringPiece& value) {
+ // Filter out headers that are invalid in SPDY.
+ if (IsInvalidSpdyResponseHeader(key)) {
+ return;
+ }
+ MergeInHeader(key, value, &headers_);
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnLeadingHeadersComplete(bool fin) {
+ if (sent_flag_fin_) {
+ LOG(DFATAL) << "Trying to send headers after sending FLAG_FIN";
+ return;
+ }
+ if (fin) {
+ sent_flag_fin_ = true;
+ }
+ receiver_->ReceiveSynReply(&headers_, fin);
+ headers_.clear();
+}
+
+void HttpToSpdyConverter::ConverterImpl::OnData(const base::StringPiece& data,
+ bool fin) {
+ data.AppendToString(&data_buffer_);
+ SendDataIfNecessary(false, fin); // false = don't flush
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataIfNecessary(bool flush,
+ bool fin) {
+ // If we have (strictly) more than one frame's worth of data waiting, send it
+ // down the filter chain, kTargetDataFrameBytes bytes at a time. If we are
+ // left with _exactly_ kTargetDataFrameBytes bytes of data, we'll deal with
+ // that in the next code block (see the comment there to explain why).
+ if (data_buffer_.size() > kTargetDataFrameBytes) {
+ const char* start = data_buffer_.data();
+ size_t size = data_buffer_.size();
+ while (size > kTargetDataFrameBytes) {
+ SendDataFrame(start, kTargetDataFrameBytes, false);
+ start += kTargetDataFrameBytes;
+ size -= kTargetDataFrameBytes;
+ }
+ data_buffer_.erase(0, data_buffer_.size() - size);
+ }
+ DCHECK(data_buffer_.size() <= kTargetDataFrameBytes);
+
+ // We may still have some leftover data. We need to send another data frame
+ // now (rather than waiting for a full kTargetDataFrameBytes) if:
+ // 1) This is the end of the response,
+ // 2) we're supposed to flush and the buffer is nonempty, or
+ // 3) we still have a full data frame's worth in the buffer.
+ //
+ // Note that because of the previous code block, condition (3) will only be
+ // true if we have exactly kTargetDataFrameBytes of data. However, dealing
+ // with that case here instead of in the above block makes it easier to make
+ // sure we correctly set FLAG_FIN on the final data frame, which is why the
+ // above block uses a strict, > comparison rather than a non-strict, >=
+ // comparison.
+ if (fin || (flush && !data_buffer_.empty()) ||
+ data_buffer_.size() >= kTargetDataFrameBytes) {
+ SendDataFrame(data_buffer_.data(), data_buffer_.size(), fin);
+ data_buffer_.clear();
+ }
+}
+
+void HttpToSpdyConverter::ConverterImpl::SendDataFrame(
+ const char* data, size_t size, bool flag_fin) {
+ if (sent_flag_fin_) {
+ LOG(DFATAL) << "Trying to send data after sending FLAG_FIN";
+ return;
+ }
+ if (flag_fin) {
+ sent_flag_fin_ = true;
+ }
+ receiver_->ReceiveData(base::StringPiece(data, size), flag_fin);
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h Thu May 1 11:43:36 2014
@@ -0,0 +1,78 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
+#define MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_piece.h"
+#include "mod_spdy/common/http_response_parser.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h" // for SpdyHeaderBlock
+
+namespace mod_spdy {
+
+// Parses incoming HTTP response data and converts it into equivalent SPDY
+// frame data.
+class HttpToSpdyConverter {
+ public:
+ // Interface for the class that will receive frame data from the converter.
+ class SpdyReceiver {
+ public:
+ SpdyReceiver();
+ virtual ~SpdyReceiver();
+
+ // Receive a SYN_REPLY frame with the given headers. The callee is free to
+ // mutate the headers map (e.g. to add an extra header) before forwarding
+ // it on, but the pointer will not remain valid after this method returns.
+ virtual void ReceiveSynReply(net::SpdyHeaderBlock* headers,
+ bool flag_fin) = 0;
+
+ // Receive a DATA frame with the given payload. The data pointer will not
+ // remain valid after this method returns.
+ virtual void ReceiveData(base::StringPiece data, bool flag_fin) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyReceiver);
+ };
+
+ // Create a converter that will send frame data to the given receiver. The
+ // converter does *not* gain ownership of the receiver.
+ HttpToSpdyConverter(spdy::SpdyVersion spdy_version, SpdyReceiver* receiver);
+ ~HttpToSpdyConverter();
+
+ // Parse and process the next chunk of input; return true on success, false
+ // on failure.
+ bool ProcessInput(base::StringPiece input_data);
+ bool ProcessInput(const char* data, size_t size) {
+ return ProcessInput(base::StringPiece(data, size));
+ }
+
+ // Flush out any buffered data.
+ void Flush();
+
+ private:
+ class ConverterImpl;
+ scoped_ptr<ConverterImpl> impl_;
+ HttpResponseParser parser_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpToSpdyConverter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_HTTP_TO_SPDY_CONVERTER_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/http_to_spdy_converter_test.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,259 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/http_to_spdy_converter.h"
+
+#include "base/string_piece.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DeleteArg;
+using testing::Eq;
+using testing::InSequence;
+using testing::Pointee;
+
+namespace {
+
+class MockSpdyReceiver : public mod_spdy::HttpToSpdyConverter::SpdyReceiver {
+ public:
+ MOCK_METHOD2(ReceiveSynReply, void(net::SpdyHeaderBlock* headers,
+ bool flag_fin));
+ MOCK_METHOD2(ReceiveData, void(base::StringPiece data, bool flag_fin));
+};
+
+class HttpToSpdyConverterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ HttpToSpdyConverterTest() : converter_(GetParam(), &receiver_) {}
+
+ protected:
+ const char* status_header_name() const {
+ return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Status :
+ mod_spdy::spdy::kSpdy3Status);
+ }
+ const char* version_header_name() const {
+ return (GetParam() < mod_spdy::spdy::SPDY_VERSION_3 ?
+ mod_spdy::spdy::kSpdy2Version :
+ mod_spdy::spdy::kSpdy3Version);
+ }
+
+ MockSpdyReceiver receiver_;
+ mod_spdy::HttpToSpdyConverter converter_;
+ net::SpdyHeaderBlock expected_headers_;
+};
+
+// Simple response with a small payload. We should get a SYN_REPLY and a DATA
+// frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithContentLength) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "14";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+ expected_headers_["x-whatever"] = "foobar";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 14\r\n"
+ "Content-Type: text/plain\r\n"
+ "X-Whatever:foobar\r\n"
+ "\r\n"
+ "Hello, world!\n"
+ "\r\n"));
+}
+
+// The data arrives in two chunks, but they're small, so we should consolidate
+// them into a single DATA frame.
+TEST_P(HttpToSpdyConverterTest, SimpleWithChunking) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "Keep-Alive: timeout=10, max=5\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "6\r\n"
+ "Hello,\r\n"
+ "8\r\n"
+ " world!\n\r\n"
+ "0\r\n"
+ "\r\n"));
+}
+
+// Test that we don't get tripped up if there is garbage after the end of
+// a chunked message.
+TEST_P(HttpToSpdyConverterTest, ChunkedEncodingWithTrailingGarbage) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("Hello, world!\n"), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "E\r\n"
+ "Hello, world!\n\r\n"
+ "0\r\n"
+ "0\r\n" // multiple last-chunks
+ "\r\n\x1bGaRbAgE")); // and also some garbage bytes
+}
+
+// No response body, so we should get the FLAG_FIN on the SYN_REPLY, and no
+// DATA frames.
+TEST_P(HttpToSpdyConverterTest, NoResponseBody) {
+ expected_headers_[status_header_name()] = "301";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_["location"] = "https://www.example.com/";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 301 Moved permenantly\r\n"
+ "Location: https://www.example.com/\r\n"
+ "\r\n"));
+}
+
+// Simple response with a large payload. We should get a SYN_REPLY and
+// multiple DATA frames.
+TEST_P(HttpToSpdyConverterTest, BreakUpLargeDataIntoMultipleFrames) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "10000";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(1808, 'x')), Eq(true)));
+
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n" +
+ std::string(10000, 'x') +
+ "\r\n"));
+}
+
+// Test that we buffer data until we get the full frame.
+TEST_P(HttpToSpdyConverterTest, BufferUntilWeHaveACompleteFrame) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "4096";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ // Send some of the headers. We shouldn't get anything out yet.
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 4096\r\n"));
+ // Send the rest of the headers, and some of the data. We should get the
+ // SYN_REPLY now, but no data yet.
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ ASSERT_TRUE(converter_.ProcessInput(
+ "Content-Type: text/plain\r\n"
+ "\r\n" +
+ std::string(2000, 'x')));
+ // Send some more data, but still not enough for a full frame.
+ ASSERT_TRUE(converter_.ProcessInput(std::string(2000, 'x')));
+ // Send the last of the data. We should finally get the one DATA frame.
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(4096, 'x')), Eq(true)));
+ ASSERT_TRUE(converter_.ProcessInput(std::string(96, 'x')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, RespectFlushes) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "4096";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ // Send the headers and some of the data (not enough for a full frame). We
+ // should get the headers out, but no data yet.
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 4096\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n" +
+ std::string(2000, 'x')));
+ // Perform a flush. We should get the data sent so far.
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2000, 'x')), Eq(false)));
+ converter_.Flush();
+ // Send the rest of the data. We should get out a second DATA frame, with
+ // FLAG_FIN set.
+ EXPECT_CALL(receiver_, ReceiveData(Eq(std::string(2096, 'y')), Eq(true)));
+ ASSERT_TRUE(converter_.ProcessInput(std::string(2096, 'y')));
+}
+
+// Test that we flush the buffer when told.
+TEST_P(HttpToSpdyConverterTest, FlushAfterEndDoesNothing) {
+ expected_headers_[status_header_name()] = "200";
+ expected_headers_[version_header_name()] = "HTTP/1.1";
+ expected_headers_[mod_spdy::http::kContentLength] = "6";
+ expected_headers_[mod_spdy::http::kContentType] = "text/plain";
+
+ InSequence seq;
+ EXPECT_CALL(receiver_, ReceiveSynReply(Pointee(Eq(expected_headers_)),
+ Eq(false)));
+ EXPECT_CALL(receiver_, ReceiveData(Eq("foobar"), Eq(true)));
+ ASSERT_TRUE(converter_.ProcessInput(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 6\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "foobar"));
+ // Flushing after we're done (even multiple times) should be permitted, but
+ // should do nothing.
+ converter_.Flush();
+ converter_.Flush();
+ converter_.Flush();
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, HttpToSpdyConverterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
Added: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,146 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/protocol_util.h"
+
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace http {
+
+extern const char* const kAcceptEncoding = "accept-encoding";
+extern const char* const kConnection = "connection";
+extern const char* const kContentLength = "content-length";
+extern const char* const kContentType = "content-type";
+extern const char* const kHost = "host";
+extern const char* const kKeepAlive = "keep-alive";
+extern const char* const kProxyConnection = "proxy-connection";
+extern const char* const kReferer = "referer";
+extern const char* const kTransferEncoding = "transfer-encoding";
+extern const char* const kXAssociatedContent = "x-associated-content";
+extern const char* const kXModSpdy = "x-mod-spdy";
+
+extern const char* const kChunked = "chunked";
+extern const char* const kGzipDeflate = "gzip,deflate";
+
+} // namespace http
+
+namespace spdy {
+
+extern const char* const kSpdy2Method = "method";
+extern const char* const kSpdy2Scheme = "scheme";
+extern const char* const kSpdy2Status = "status";
+extern const char* const kSpdy2Url = "url";
+extern const char* const kSpdy2Version = "version";
+
+extern const char* const kSpdy3Host = ":host";
+extern const char* const kSpdy3Method = ":method";
+extern const char* const kSpdy3Path = ":path";
+extern const char* const kSpdy3Scheme = ":scheme";
+extern const char* const kSpdy3Status = ":status";
+extern const char* const kSpdy3Version = ":version";
+
+} // namespace spdy
+
+int SpdyVersionToFramerVersion(spdy::SpdyVersion version) {
+ switch (version) {
+ case spdy::SPDY_VERSION_NONE:
+ return 0;
+ case spdy::SPDY_VERSION_2:
+ return 2;
+ case spdy::SPDY_VERSION_3:
+ case spdy::SPDY_VERSION_3_1:
+ return 3;
+ default:
+ LOG(DFATAL) << "Invalid SpdyVersion value: " << version;
+ return -1;
+ }
+}
+
+const char* SpdyVersionNumberString(spdy::SpdyVersion version) {
+ switch (version) {
+ case spdy::SPDY_VERSION_2: return "2";
+ case spdy::SPDY_VERSION_3: return "3";
+ case spdy::SPDY_VERSION_3_1: return "3.1";
+ default:
+ LOG(DFATAL) << "Invalid SpdyVersion value: " << version;
+ return "?";
+ }
+}
+
+const char* GoAwayStatusCodeToString(net::SpdyGoAwayStatus status) {
+ switch (status) {
+ case net::GOAWAY_OK: return "OK";
+ case net::GOAWAY_PROTOCOL_ERROR: return "PROTOCOL_ERROR";
+ case net::GOAWAY_INTERNAL_ERROR: return "INTERNAL_ERROR";
+ default: return "<unknown>";
+ }
+}
+
+const char* SettingsIdToString(net::SpdySettingsIds id) {
+ switch (id) {
+ case net::SETTINGS_UPLOAD_BANDWIDTH: return "UPLOAD_BANDWIDTH";
+ case net::SETTINGS_DOWNLOAD_BANDWIDTH: return "DOWNLOAD_BANDWIDTH";
+ case net::SETTINGS_ROUND_TRIP_TIME: return "ROUND_TRIP_TIME";
+ case net::SETTINGS_MAX_CONCURRENT_STREAMS: return "MAX_CONCURRENT_STREAMS";
+ case net::SETTINGS_CURRENT_CWND: return "CURRENT_CWND";
+ case net::SETTINGS_DOWNLOAD_RETRANS_RATE: return "DOWNLOAD_RETRANS_RATE";
+ case net::SETTINGS_INITIAL_WINDOW_SIZE: return "INITIAL_WINDOW_SIZE";
+ default: return "<unknown>";
+ }
+}
+
+base::StringPiece FrameData(const net::SpdyFrame& frame) {
+ return base::StringPiece(
+ frame.data(), frame.length() + net::SpdyFrame::kHeaderSize);
+}
+
+bool IsInvalidSpdyResponseHeader(base::StringPiece key) {
+ // The following headers are forbidden in SPDY responses (SPDY draft 3
+ // section 3.2.2).
+ return (LowerCaseEqualsASCII(key.begin(), key.end(), http::kConnection) ||
+ LowerCaseEqualsASCII(key.begin(), key.end(), http::kKeepAlive) ||
+ LowerCaseEqualsASCII(key.begin(), key.end(),
+ http::kProxyConnection) ||
+ LowerCaseEqualsASCII(key.begin(), key.end(),
+ http::kTransferEncoding));
+}
+
+net::SpdyPriority LowestSpdyPriorityForVersion(
+ spdy::SpdyVersion spdy_version) {
+ return (spdy_version < spdy::SPDY_VERSION_3 ? 3u : 7u);
+}
+
+void MergeInHeader(base::StringPiece key, base::StringPiece value,
+ net::SpdyHeaderBlock* headers) {
+ // The SPDY spec requires that header names be lowercase, so forcibly
+ // lowercase the key here.
+ std::string lower_key(key.as_string());
+ StringToLowerASCII(&lower_key);
+
+ net::SpdyHeaderBlock::iterator iter = headers->find(lower_key);
+ if (iter == headers->end()) {
+ (*headers)[lower_key] = value.as_string();
+ } else {
+ iter->second.push_back('\0');
+ value.AppendToString(&iter->second);
+ }
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h Thu May 1 11:43:36 2014
@@ -0,0 +1,107 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
+#define MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
+
+#include "base/string_piece.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+namespace http {
+
+// HTTP header names. These values are all lower-case, so they can be used
+// directly in SPDY header blocks.
+extern const char* const kAcceptEncoding;
+extern const char* const kConnection;
+extern const char* const kContentLength;
+extern const char* const kContentType;
+extern const char* const kHost;
+extern const char* const kKeepAlive;
+extern const char* const kProxyConnection;
+extern const char* const kReferer;
+extern const char* const kTransferEncoding;
+extern const char* const kXAssociatedContent;
+extern const char* const kXModSpdy;
+
+// HTTP header values.
+extern const char* const kChunked;
+extern const char* const kGzipDeflate;
+
+} // namespace http
+
+namespace spdy {
+
+// Represents a specific SPDY version, including experimental versions such as
+// SPDY/3.1 (which uses version 3 frames, but has extra semantics borrowed from
+// SPDY/4).
+enum SpdyVersion {
+ SPDY_VERSION_NONE, // not using SPDY
+ SPDY_VERSION_2, // SPDY/2
+ SPDY_VERSION_3, // SPDY/3
+ SPDY_VERSION_3_1 // SPDY/3.1 (SPDY/3 framing, but with new flow control)
+};
+
+// Magic header names for SPDY v2.
+extern const char* const kSpdy2Method;
+extern const char* const kSpdy2Scheme;
+extern const char* const kSpdy2Status;
+extern const char* const kSpdy2Url;
+extern const char* const kSpdy2Version;
+
+// Magic header names for SPDY v3.
+extern const char* const kSpdy3Host;
+extern const char* const kSpdy3Method;
+extern const char* const kSpdy3Path;
+extern const char* const kSpdy3Scheme;
+extern const char* const kSpdy3Status;
+extern const char* const kSpdy3Version;
+
+} // namespace spdy
+
+// Given a SpdyVersion enum value, return the framer version number to use.
+// Returns zero for SPDY_VERSION_NONE.
+int SpdyVersionToFramerVersion(spdy::SpdyVersion version);
+
+// Given a SpdyVersion enum value (other than SPDY_VERSION_NONE), return a
+// string for the version number (e.g. "3" or "3.1").
+const char* SpdyVersionNumberString(spdy::SpdyVersion version);
+
+// Convert various SPDY enum types to strings.
+const char* GoAwayStatusCodeToString(net::SpdyGoAwayStatus status);
+inline const char* RstStreamStatusCodeToString(net::SpdyStatusCodes status) {
+ return net::SpdyFramer::StatusCodeToString(status);
+}
+const char* SettingsIdToString(net::SpdySettingsIds id);
+
+// Return a view of the raw bytes of the frame.
+base::StringPiece FrameData(const net::SpdyFrame& frame);
+
+// Return true if this header is forbidden in SPDY responses (ignoring case).
+bool IsInvalidSpdyResponseHeader(base::StringPiece key);
+
+// Return the SpdyPriority representing the least important priority for the
+// given SPDY version. For SPDY v2 and below, it's 3; for SPDY v3 and above,
+// it's 7. (The most important SpdyPriority is always 0.)
+net::SpdyPriority LowestSpdyPriorityForVersion(spdy::SpdyVersion spdy_version);
+
+// Add a header to a header table, lower-casing and merging if necessary.
+void MergeInHeader(base::StringPiece key, base::StringPiece value,
+ net::SpdyHeaderBlock* headers);
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_PROTOCOL_UTIL_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/protocol_util_test.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,95 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/protocol_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(ProtocolUtilTest, InvalidSpdyResponseHeaders) {
+ // Forbidden headers should be rejected regardless of capitalization.
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("connection"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("Connection"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("cOnNeCtIoN"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("transfer-encoding"));
+ EXPECT_TRUE(mod_spdy::IsInvalidSpdyResponseHeader("Transfer-Encoding"));
+}
+
+TEST(ProtocolUtilTest, ValidSpdyResponseHeaders) {
+ // Permitted headers should be accepted regardless of capitalization (SPDY
+ // requires header names to be lowercase, but this function shouldn't be
+ // checking that).
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader("content-length"));
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader("Content-Length"));
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader(
+ "x-header-we-have-never-heard-of"));
+ EXPECT_FALSE(mod_spdy::IsInvalidSpdyResponseHeader(
+ "X-HEADER-WE-HAVE-NEVER-HEARD-OF"));
+}
+
+TEST(ProtocolUtilTest, MergeIntoEmpty) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("content-length", "256", &headers);
+ ASSERT_EQ(1u, headers.size());
+ ASSERT_EQ("256", headers["content-length"]);
+}
+
+TEST(ProtocolUtilTest, MakeLowerCase) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("Content-Length", "256", &headers);
+ ASSERT_EQ(1u, headers.size());
+ ASSERT_EQ(0u, headers.count("Content-Length"));
+ ASSERT_EQ("256", headers["content-length"]);
+}
+
+TEST(ProtocolUtilTest, MergeDifferentHeaders) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("x-foo", "bar", &headers);
+ ASSERT_EQ(1u, headers.size());
+ ASSERT_EQ("bar", headers["x-foo"]);
+
+ mod_spdy::MergeInHeader("x-baz", "quux", &headers);
+ ASSERT_EQ(2u, headers.size());
+ ASSERT_EQ("bar", headers["x-foo"]);
+ ASSERT_EQ("quux", headers["x-baz"]);
+}
+
+TEST(ProtocolUtilTest, MergeRepeatedHeader) {
+ net::SpdyHeaderBlock headers;
+ ASSERT_EQ(0u, headers.size());
+
+ mod_spdy::MergeInHeader("x-foo", "bar", &headers);
+ ASSERT_EQ(1u, headers.size());
+ const std::string expected1("bar");
+ ASSERT_EQ(expected1, headers["x-foo"]);
+
+ mod_spdy::MergeInHeader("x-foo", "baz", &headers);
+ ASSERT_EQ(1u, headers.size());
+ const std::string expected2("bar\0baz", 7);
+ ASSERT_EQ(expected2, headers["x-foo"]);
+
+ mod_spdy::MergeInHeader("x-foo", "quux", &headers);
+ ASSERT_EQ(1u, headers.size());
+ const std::string expected3("bar\0baz\0quux", 12);
+ ASSERT_EQ(expected3, headers["x-foo"]);
+}
+
+} // namespace
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,120 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+
+#include <list>
+#include <map>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SpdyFramePriorityQueue::SpdyFramePriorityQueue()
+ : condvar_(&lock_) {}
+
+SpdyFramePriorityQueue::~SpdyFramePriorityQueue() {
+ for (QueueMap::iterator iter = queue_map_.begin();
+ iter != queue_map_.end(); ++iter) {
+ FrameList* list = iter->second;
+ STLDeleteContainerPointers(list->begin(), list->end());
+ delete list;
+ }
+}
+
+bool SpdyFramePriorityQueue::IsEmpty() const {
+ base::AutoLock autolock(lock_);
+ return queue_map_.empty();
+}
+
+const int SpdyFramePriorityQueue::kTopPriority = -1;
+
+void SpdyFramePriorityQueue::Insert(int priority, net::SpdyFrame* frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ // Get the frame list for the given priority; if it doesn't currently exist,
+ // create it in the map.
+ FrameList* list = NULL;
+ QueueMap::iterator iter = queue_map_.find(priority);
+ if (iter == queue_map_.end()) {
+ list = new FrameList;
+ queue_map_[priority] = list;
+ } else {
+ list = iter->second;
+ }
+ DCHECK(list);
+
+ // Add the frame to the end of the list, and wake up at most one thread
+ // sleeping on a BlockingPop.
+ list->push_back(frame);
+ condvar_.Signal();
+}
+
+bool SpdyFramePriorityQueue::Pop(net::SpdyFrame** frame) {
+ base::AutoLock autolock(lock_);
+ return InternalPop(frame);
+}
+
+bool SpdyFramePriorityQueue::BlockingPop(const base::TimeDelta& max_time,
+ net::SpdyFrame** frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ const base::TimeDelta zero = base::TimeDelta();
+ base::TimeDelta time_remaining = max_time;
+ while (time_remaining > zero && queue_map_.empty()) {
+ // TODO(mdsteele): It appears from looking at the Chromium source code that
+ // HighResNow() is "expensive" on Windows (how expensive, I am not sure);
+ // however, the other options for getting a "now" time either don't
+ // guarantee monotonicity (so time might go backwards) or might be too
+ // low-resolution for our purposes, so I think we'd better stick with this
+ // for now. But is there a better way to do what we're doing here?
+ const base::TimeTicks start = base::TimeTicks::HighResNow();
+ condvar_.TimedWait(time_remaining);
+ time_remaining -= base::TimeTicks::HighResNow() - start;
+ }
+
+ return InternalPop(frame);
+}
+
+bool SpdyFramePriorityQueue::InternalPop(net::SpdyFrame** frame) {
+ lock_.AssertAcquired();
+ DCHECK(frame);
+ if (queue_map_.empty()) {
+ return false;
+ }
+ // As an invariant, the lists in the queue map are never empty. So get the
+ // list of highest priority (smallest priority number) and pop the first
+ // frame from it.
+ QueueMap::iterator iter = queue_map_.begin();
+ FrameList* list = iter->second;
+ DCHECK(!list->empty());
+ *frame = list->front();
+ list->pop_front();
+ // If the list is now empty, we have to delete it from the map to maintain
+ // the invariant.
+ if (list->empty()) {
+ queue_map_.erase(iter);
+ delete list;
+ }
+ return true;
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h Thu May 1 11:43:36 2014
@@ -0,0 +1,95 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
+#define MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace base { class TimeDelta; }
+
+namespace net { class SpdyFrame; }
+
+namespace mod_spdy {
+
+// A priority queue of SPDY frames, intended for multiplexing output frames
+// from multiple SPDY stream threads back to the SPDY connection thread and
+// allowing frames from high-priority streams to cut in front of lower-priority
+// streams. This class is thread-safe -- its methods may be called
+// concurrently by multiple threads.
+class SpdyFramePriorityQueue {
+ public:
+ // Create an initially-empty queue.
+ SpdyFramePriorityQueue();
+ ~SpdyFramePriorityQueue();
+
+ // Return true if the queue is currently empty. (Of course, there's no
+ // guarantee that another thread won't change that as soon as this method
+ // returns.)
+ bool IsEmpty() const;
+
+ // A priority value that is more important than any priority normally used
+ // for sending SPDY frames.
+ static const int kTopPriority;
+
+ // Insert a frame into the queue at the specified priority. The queue takes
+ // ownership of the frame, and will delete it if the queue is deleted before
+ // the frame is removed from the queue by the Pop method. Note that smaller
+ // numbers indicate higher priorities.
+ void Insert(int priority, net::SpdyFrame* frame);
+
+ // Remove and provide a frame from the queue and return true, or return false
+ // if the queue is empty. The caller gains ownership of the provided frame
+ // object. This method will try to yield higher-priority frames before
+ // lower-priority ones (even if they were inserted later), but guarantees to
+ // return same-priority frames in the same order they were inserted (FIFO).
+ // In particular, this means that a sequence of frames from the same SPDY
+ // stream will stay in order (assuming they were all inserted with the same
+ // priority -- that of the stream).
+ bool Pop(net::SpdyFrame** frame);
+
+ // Like Pop(), but if the queue is empty this method will block for up to
+ // max_time before returning false.
+ bool BlockingPop(const base::TimeDelta& max_time, net::SpdyFrame** frame);
+
+ private:
+ // Same as Pop(), but requires lock_ to be held.
+ bool InternalPop(net::SpdyFrame** frame);
+
+ mutable base::Lock lock_;
+ base::ConditionVariable condvar_;
+ // We use a map of lists to store frames, to guarantee that frames of the
+ // same priority are stored in FIFO order. A simpler implementation would be
+ // to just use a multimap, which in practice is nearly always implemented
+ // with the FIFO behavior that we want, but the spec doesn't actually
+ // guarantee that behavior.
+ //
+ // Each list stores frames of a particular priority. Invariant: the lists in
+ // the QueueMap are never empty; if one of the lists becomes empty, that
+ // key/value pair is immediately removed from the map.
+ typedef std::list<net::SpdyFrame*> FrameList;
+ typedef std::map<int, FrameList*> QueueMap;
+ QueueMap queue_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFramePriorityQueue);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_FRAME_PRIORITY_QUEUE_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_priority_queue_test.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,147 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_priority_queue.h"
+
+#include "base/time.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+net::SpdyStreamId GetPingId(net::SpdyFrame* frame) {
+ if (!frame->is_control_frame() ||
+ static_cast<net::SpdyControlFrame*>(frame)->type() != net::PING) {
+ ADD_FAILURE() << "Frame is not a PING frame.";
+ return 0;
+ }
+ return static_cast<net::SpdyPingControlFrame*>(frame)->unique_id();
+}
+
+void ExpectPop(net::SpdyStreamId expected,
+ mod_spdy::SpdyFramePriorityQueue* queue) {
+ EXPECT_FALSE(queue->IsEmpty());
+ net::SpdyFrame* raw_frame = NULL;
+ const bool success = queue->Pop(&raw_frame);
+ scoped_ptr<net::SpdyFrame> scoped_frame(raw_frame);
+ EXPECT_TRUE(success);
+ ASSERT_TRUE(scoped_frame != NULL);
+ ASSERT_EQ(expected, GetPingId(scoped_frame.get()));
+}
+
+void ExpectEmpty(mod_spdy::SpdyFramePriorityQueue* queue) {
+ EXPECT_TRUE(queue->IsEmpty());
+ net::SpdyFrame* frame = NULL;
+ EXPECT_FALSE(queue->Pop(&frame));
+ EXPECT_TRUE(frame == NULL);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertSpdy2) {
+ net::SpdyFramer framer(2);
+ mod_spdy::SpdyFramePriorityQueue queue;
+ ExpectEmpty(&queue);
+
+ EXPECT_EQ(3u, framer.GetLowestPriority());
+ EXPECT_EQ(0u, framer.GetHighestPriority());
+
+ queue.Insert(3, framer.CreatePingFrame(4));
+ queue.Insert(0, framer.CreatePingFrame(1));
+ queue.Insert(3, framer.CreatePingFrame(3));
+
+ ExpectPop(1, &queue);
+ ExpectPop(4, &queue);
+
+ queue.Insert(2, framer.CreatePingFrame(2));
+ queue.Insert(1, framer.CreatePingFrame(6));
+ queue.Insert(1, framer.CreatePingFrame(5));
+
+ ExpectPop(6, &queue);
+ ExpectPop(5, &queue);
+ ExpectPop(2, &queue);
+ ExpectPop(3, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertSpdy3) {
+ net::SpdyFramer framer(3);
+ mod_spdy::SpdyFramePriorityQueue queue;
+ ExpectEmpty(&queue);
+
+ EXPECT_EQ(7u, framer.GetLowestPriority());
+ EXPECT_EQ(0u, framer.GetHighestPriority());
+
+ queue.Insert(7, framer.CreatePingFrame(4));
+ queue.Insert(0, framer.CreatePingFrame(1));
+ queue.Insert(7, framer.CreatePingFrame(3));
+
+ ExpectPop(1, &queue);
+ ExpectPop(4, &queue);
+
+ queue.Insert(6, framer.CreatePingFrame(2));
+ queue.Insert(1, framer.CreatePingFrame(6));
+ queue.Insert(5, framer.CreatePingFrame(5));
+
+ ExpectPop(6, &queue);
+ ExpectPop(5, &queue);
+ ExpectPop(2, &queue);
+ ExpectPop(3, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, InsertTopPriority) {
+ net::SpdyFramer framer(2);
+ mod_spdy::SpdyFramePriorityQueue queue;
+ ExpectEmpty(&queue);
+
+ queue.Insert(3, framer.CreatePingFrame(4));
+ queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+ framer.CreatePingFrame(2));
+ queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+ framer.CreatePingFrame(6));
+ queue.Insert(0, framer.CreatePingFrame(1));
+ queue.Insert(3, framer.CreatePingFrame(3));
+
+ ExpectPop(2, &queue);
+ ExpectPop(6, &queue);
+ ExpectPop(1, &queue);
+ ExpectPop(4, &queue);
+
+ queue.Insert(mod_spdy::SpdyFramePriorityQueue::kTopPriority,
+ framer.CreatePingFrame(5));
+
+ ExpectPop(5, &queue);
+ ExpectPop(3, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFramePriorityQueueTest, BlockingPop) {
+ mod_spdy::SpdyFramePriorityQueue queue;
+ net::SpdyFrame* frame;
+ ASSERT_FALSE(queue.Pop(&frame));
+
+ const base::TimeDelta time_to_wait = base::TimeDelta::FromMilliseconds(50);
+ const base::TimeTicks start = base::TimeTicks::HighResNow();
+ ASSERT_FALSE(queue.BlockingPop(time_to_wait, &frame));
+ const base::TimeDelta actual_time_waited =
+ base::TimeTicks::HighResNow() - start;
+
+ // Check that we waited at least as long as we asked for.
+ EXPECT_GE(actual_time_waited, time_to_wait);
+ // Check that we didn't wait too much longer than we asked for.
+ EXPECT_LT(actual_time_waited.InMillisecondsF(),
+ 1.1 * time_to_wait.InMillisecondsF());
+}
+
+} // namespace
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,84 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_queue.h"
+
+#include <list>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+SpdyFrameQueue::SpdyFrameQueue()
+ : condvar_(&lock_), is_aborted_(false) {}
+
+SpdyFrameQueue::~SpdyFrameQueue() {
+ STLDeleteContainerPointers(queue_.begin(), queue_.end());
+}
+
+bool SpdyFrameQueue::is_aborted() const {
+ base::AutoLock autolock(lock_);
+ return is_aborted_;
+}
+
+void SpdyFrameQueue::Abort() {
+ base::AutoLock autolock(lock_);
+ is_aborted_ = true;
+ STLDeleteContainerPointers(queue_.begin(), queue_.end());
+ queue_.clear();
+ condvar_.Broadcast();
+}
+
+void SpdyFrameQueue::Insert(net::SpdyFrame* frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ if (is_aborted_) {
+ DCHECK(queue_.empty());
+ delete frame;
+ } else {
+ if (queue_.empty()) {
+ condvar_.Signal();
+ }
+ queue_.push_front(frame);
+ }
+}
+
+bool SpdyFrameQueue::Pop(bool block, net::SpdyFrame** frame) {
+ base::AutoLock autolock(lock_);
+ DCHECK(frame);
+
+ if (block) {
+ // Block until the queue is nonempty or we abort.
+ while (queue_.empty() && !is_aborted_) {
+ condvar_.Wait();
+ }
+ }
+
+ // If we've aborted, the queue should now be empty.
+ DCHECK(!is_aborted_ || queue_.empty());
+ if (queue_.empty()) {
+ return false;
+ }
+
+ *frame = queue_.back();
+ queue_.pop_back();
+ return true;
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h Thu May 1 11:43:36 2014
@@ -0,0 +1,71 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
+#define MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace net { class SpdyFrame; }
+
+namespace mod_spdy {
+
+// A simple FIFO queue of SPDY frames, intended for sending input frames from
+// the SPDY connection thread to a SPDY stream thread. This class is
+// thread-safe -- all methods may be called concurrently by multiple threads.
+class SpdyFrameQueue {
+ public:
+ // Create an initially-empty queue.
+ SpdyFrameQueue();
+ ~SpdyFrameQueue();
+
+ // Return true if this queue has been aborted.
+ bool is_aborted() const;
+
+ // Abort the queue. All frames held by the queue will be deleted; future
+ // frames passed to Insert() will be immediately deleted; future calls to
+ // Pop() will fail immediately; and current blocking calls to Pop will
+ // immediately unblock and fail.
+ void Abort();
+
+ // Insert a frame into the queue. The queue takes ownership of the frame,
+ // and will delete it if the queue is deleted or aborted before the frame is
+ // removed from the queue by the Pop method.
+ void Insert(net::SpdyFrame* frame);
+
+ // Remove and provide a frame from the queue and return true, or return false
+ // if the queue is empty or has been aborted. If the block argument is true,
+ // block until a frame becomes available (or the queue is aborted). The
+ // caller gains ownership of the provided frame object.
+ bool Pop(bool block, net::SpdyFrame** frame);
+
+ private:
+ // This is a pretty naive implementation of a thread-safe queue, but it's
+ // good enough for our purposes. We could use an apr_queue_t instead of
+ // rolling our own class, but it lacks the ownership semantics that we want.
+ mutable base::Lock lock_;
+ base::ConditionVariable condvar_;
+ std::list<net::SpdyFrame*> queue_;
+ bool is_aborted_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameQueue);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_FRAME_QUEUE_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_frame_queue_test.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,123 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_frame_queue.h"
+
+#include "base/basictypes.h"
+#include "base/threading/platform_thread.h"
+#include "mod_spdy/common/testing/async_task_runner.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int kSpdyVersion = 2;
+
+net::SpdyStreamId GetPingId(net::SpdyFrame* frame) {
+ if (!frame->is_control_frame() ||
+ static_cast<net::SpdyControlFrame*>(frame)->type() != net::PING) {
+ ADD_FAILURE() << "Frame is not a PING frame.";
+ return 0;
+ }
+ return static_cast<net::SpdyPingControlFrame*>(frame)->unique_id();
+}
+
+void ExpectPop(bool block, net::SpdyStreamId expected,
+ mod_spdy::SpdyFrameQueue* queue) {
+ net::SpdyFrame* raw_frame = NULL;
+ const bool success = queue->Pop(block, &raw_frame);
+ scoped_ptr<net::SpdyFrame> scoped_frame(raw_frame);
+ EXPECT_TRUE(success);
+ ASSERT_TRUE(scoped_frame != NULL);
+ ASSERT_EQ(expected, GetPingId(scoped_frame.get()));
+}
+
+void ExpectEmpty(mod_spdy::SpdyFrameQueue* queue) {
+ net::SpdyFrame* frame = NULL;
+ EXPECT_FALSE(queue->Pop(false, &frame));
+ EXPECT_TRUE(frame == NULL);
+}
+
+TEST(SpdyFrameQueueTest, Simple) {
+ net::SpdyFramer framer(kSpdyVersion);
+ mod_spdy::SpdyFrameQueue queue;
+ ExpectEmpty(&queue);
+
+ queue.Insert(framer.CreatePingFrame(4));
+ queue.Insert(framer.CreatePingFrame(1));
+ queue.Insert(framer.CreatePingFrame(3));
+
+ ExpectPop(false, 4, &queue);
+ ExpectPop(false, 1, &queue);
+
+ queue.Insert(framer.CreatePingFrame(2));
+ queue.Insert(framer.CreatePingFrame(5));
+
+ ExpectPop(false, 3, &queue);
+ ExpectPop(false, 2, &queue);
+ ExpectPop(false, 5, &queue);
+ ExpectEmpty(&queue);
+}
+
+TEST(SpdyFrameQueueTest, AbortEmptiesQueue) {
+ net::SpdyFramer framer(kSpdyVersion);
+ mod_spdy::SpdyFrameQueue queue;
+ ASSERT_FALSE(queue.is_aborted());
+ ExpectEmpty(&queue);
+
+ queue.Insert(framer.CreatePingFrame(4));
+ queue.Insert(framer.CreatePingFrame(1));
+ queue.Insert(framer.CreatePingFrame(3));
+
+ ExpectPop(false, 4, &queue);
+
+ queue.Abort();
+
+ ExpectEmpty(&queue);
+ ASSERT_TRUE(queue.is_aborted());
+}
+
+class BlockingPopTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+ explicit BlockingPopTask(mod_spdy::SpdyFrameQueue* queue) : queue_(queue) {}
+ virtual void Run() { ExpectPop(true, 7, queue_); }
+ private:
+ mod_spdy::SpdyFrameQueue* const queue_;
+ DISALLOW_COPY_AND_ASSIGN(BlockingPopTask);
+};
+
+TEST(SpdyFrameQueueTest, BlockingPop) {
+ net::SpdyFramer framer(kSpdyVersion);
+ mod_spdy::SpdyFrameQueue queue;
+
+ // Start a task that will do a blocking pop from the queue.
+ mod_spdy::testing::AsyncTaskRunner runner(new BlockingPopTask(&queue));
+ ASSERT_TRUE(runner.Start());
+
+ // Even if we wait for a little bit, the task shouldn't complete, because
+ // that thread is blocked, because the queue is still empty.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
+ runner.notification()->ExpectNotSet();
+ ExpectEmpty(&queue);
+
+ // Now, if we push something into the queue, the task should soon unblock and
+ // complete, and the queue should then be empty.
+ queue.Insert(framer.CreatePingFrame(7));
+ runner.notification()->ExpectSetWithinMillis(100);
+ ExpectEmpty(&queue);
+}
+
+} // namespace
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,65 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_server_config.h"
+
+#include "mod_spdy/common/protocol_util.h"
+
+namespace {
+
+const bool kDefaultSpdyEnabled = false;
+const int kDefaultMaxStreamsPerConnection = 100;
+const int kDefaultMinThreadsPerProcess = 2;
+const int kDefaultMaxThreadsPerProcess = 10;
+const int kDefaultMaxServerPushDepth = 1;
+const bool kDefaultSendVersionHeader = true;
+const mod_spdy::spdy::SpdyVersion kDefaultUseSpdyVersionWithoutSsl =
+ mod_spdy::spdy::SPDY_VERSION_NONE;
+const int kDefaultVlogLevel = 0;
+
+} // namespace
+
+namespace mod_spdy {
+
+SpdyServerConfig::SpdyServerConfig()
+ : spdy_enabled_(kDefaultSpdyEnabled),
+ max_streams_per_connection_(kDefaultMaxStreamsPerConnection),
+ min_threads_per_process_(kDefaultMinThreadsPerProcess),
+ max_threads_per_process_(kDefaultMaxThreadsPerProcess),
+ max_server_push_depth_(kDefaultMaxServerPushDepth),
+ send_version_header_(kDefaultSendVersionHeader),
+ use_spdy_version_without_ssl_(kDefaultUseSpdyVersionWithoutSsl),
+ vlog_level_(kDefaultVlogLevel) {}
+
+SpdyServerConfig::~SpdyServerConfig() {}
+
+void SpdyServerConfig::MergeFrom(const SpdyServerConfig& a,
+ const SpdyServerConfig& b) {
+ spdy_enabled_.MergeFrom(a.spdy_enabled_, b.spdy_enabled_);
+ max_streams_per_connection_.MergeFrom(a.max_streams_per_connection_,
+ b.max_streams_per_connection_);
+ min_threads_per_process_.MergeFrom(a.min_threads_per_process_,
+ b.min_threads_per_process_);
+ max_threads_per_process_.MergeFrom(a.max_threads_per_process_,
+ b.max_threads_per_process_);
+ max_server_push_depth_.MergeFrom(a.max_server_push_depth_,
+ b.max_server_push_depth_);
+ send_version_header_.MergeFrom(
+ a.send_version_header_, b.send_version_header_);
+ use_spdy_version_without_ssl_.MergeFrom(
+ a.use_spdy_version_without_ssl_, b.use_spdy_version_without_ssl_);
+ vlog_level_.MergeFrom(a.vlog_level_, b.vlog_level_);
+}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h Thu May 1 11:43:36 2014
@@ -0,0 +1,121 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SERVER_CONFIG_H_
+#define MOD_SPDY_COMMON_SPDY_SERVER_CONFIG_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/protocol_util.h"
+
+namespace mod_spdy {
+
+// Stores server configuration settings for our module.
+class SpdyServerConfig {
+ public:
+ SpdyServerConfig();
+ ~SpdyServerConfig();
+
+ // Return true if SPDY is enabled for this server, false otherwise.
+ bool spdy_enabled() const { return spdy_enabled_.get(); }
+
+ // Return the maximum number of simultaneous SPDY streams that should be
+ // permitted for a single client connection.
+ int max_streams_per_connection() const {
+ return max_streams_per_connection_.get();
+ }
+
+ // Return the minimum number of worker threads to spawn per child process.
+ int min_threads_per_process() const {
+ return min_threads_per_process_.get();
+ }
+
+ // Return the maximum number of worker threads to spawn per child process.
+ int max_threads_per_process() const {
+ return max_threads_per_process_.get();
+ }
+
+ // Return the maximum number of recursive levels to follow
+ // X-Associated-Content headers
+ int max_server_push_depth() const {
+ return max_server_push_depth_.get();
+ }
+
+ // Whether or not we should include an x-mod-spdy header with the module
+ // version number.
+ bool send_version_header() const { return send_version_header_.get(); }
+
+ // If nonzero, assume (unencrypted) SPDY/x for non-SSL connections, where x
+ // is the version number returned here. This will most likely break normal
+ // browsers, but is useful for testing.
+ spdy::SpdyVersion use_spdy_version_without_ssl() const {
+ return use_spdy_version_without_ssl_.get();
+ }
+
+ // Return the maximum VLOG level we should use.
+ int vlog_level() const { return vlog_level_.get(); }
+
+ // Setters. Call only during the configuration phase.
+ void set_spdy_enabled(bool b) { spdy_enabled_.set(b); }
+ void set_max_streams_per_connection(int n) {
+ max_streams_per_connection_.set(n);
+ }
+ void set_min_threads_per_process(int n) { min_threads_per_process_.set(n); }
+ void set_max_threads_per_process(int n) { max_threads_per_process_.set(n); }
+ void set_max_server_push_depth(int n) { max_server_push_depth_.set(n); }
+ void set_send_version_header(bool b) { send_version_header_.set(b); }
+ void set_use_spdy_version_without_ssl(spdy::SpdyVersion v) {
+ use_spdy_version_without_ssl_.set(v);
+ }
+ void set_vlog_level(int n) { vlog_level_.set(n); }
+
+ // Set this config object to the merge of a and b. Call only during the
+ // configuration phase.
+ void MergeFrom(const SpdyServerConfig& a, const SpdyServerConfig& b);
+
+ private:
+ template <typename T>
+ class Option {
+ public:
+ explicit Option(const T& default_value)
+ : was_set_(false), value_(default_value) {}
+ const T& get() const { return value_; }
+ void set(const T& value) { was_set_ = true; value_ = value; }
+ void MergeFrom(const Option<T>& a, const Option<T>& b) {
+ was_set_ = a.was_set_ || b.was_set_;
+ value_ = a.was_set_ ? a.value_ : b.value_;
+ }
+ private:
+ bool was_set_;
+ T value_;
+ DISALLOW_COPY_AND_ASSIGN(Option);
+ };
+
+ // Configuration fields:
+ Option<bool> spdy_enabled_;
+ Option<int> max_streams_per_connection_;
+ Option<int> min_threads_per_process_;
+ Option<int> max_threads_per_process_;
+ Option<int> max_server_push_depth_;
+ Option<bool> send_version_header_;
+ Option<spdy::SpdyVersion> use_spdy_version_without_ssl_;
+ Option<int> vlog_level_;
+ // Note: Add more config options here as needed; be sure to also update the
+ // MergeFrom method in spdy_server_config.cc.
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyServerConfig);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_CONTEXT_SPDY_SERVER_CONFIG_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_config.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.cc Thu May 1 11:43:36 2014
@@ -0,0 +1,23 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/spdy_server_push_interface.h"
+
+namespace mod_spdy {
+
+SpdyServerPushInterface::SpdyServerPushInterface() {}
+
+SpdyServerPushInterface::~SpdyServerPushInterface() {}
+
+} // namespace mod_spdy
Added: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h?rev=1591622&view=auto
==============================================================================
--- httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h (added)
+++ httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h Thu May 1 11:43:36 2014
@@ -0,0 +1,66 @@
+// Copyright 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
+#define MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class SpdyServerPushInterface {
+ public:
+ SpdyServerPushInterface();
+ virtual ~SpdyServerPushInterface();
+
+ enum PushStatus {
+ // PUSH_STARTED: The server push was started successfully.
+ PUSH_STARTED,
+ // INVALID_REQUEST_HEADERS: The given request headers were invalid for a
+ // server push (e.g. because required headers were missing).
+ INVALID_REQUEST_HEADERS,
+ // ASSOCIATED_STREAM_INACTIVE: The push could not be started because the
+ // associated stream is not currently active.
+ ASSOCIATED_STREAM_INACTIVE,
+ // CANNOT_PUSH_EVER_AGAIN: We can't do any more pushes on this session,
+ // either because the client has already sent us a GOAWAY frame, or the
+ // session has been open so long that we've run out of stream IDs.
+ CANNOT_PUSH_EVER_AGAIN,
+ // TOO_MANY_CONCURRENT_PUSHES: The push could not be started right now
+ // because there are too many currently active push streams.
+ TOO_MANY_CONCURRENT_PUSHES,
+ // PUSH_INTERNAL_ERROR: There was an internal error in the SpdySession
+ // (typically something that caused a LOG(DFATAL).
+ PUSH_INTERNAL_ERROR,
+ };
+
+ // Initiate a SPDY server push, roughly by pretending that the client sent a
+ // SYN_STREAM with the given headers. To repeat: the headers argument is
+ // _not_ the headers that the server will send to the client, but rather the
+ // headers to _pretend_ that the client sent to the server.
+ virtual PushStatus StartServerPush(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyHeaderBlock& request_headers) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyServerPushInterface);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_SPDY_SERVER_PUSH_INTERFACE_H_
Propchange: httpd/mod_spdy/trunk/mod_spdy/common/spdy_server_push_interface.h
------------------------------------------------------------------------------
svn:eol-style = native