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/04/28 12:55:22 UTC
svn commit: r1590597 [8/14] - in /httpd/httpd/trunk/modules/spdy: ./ apache/
apache/filters/ apache/testing/ common/ common/testing/ support/
support/base/ support/base/metrics/ support/build/ support/install/
support/install/common/ support/install/de...
Added: httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_stream_test.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,596 @@
+// 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_stream.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/shared_flow_control_window.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/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using mod_spdy::testing::IsDataFrame;
+using mod_spdy::testing::IsRstStream;
+using mod_spdy::testing::IsWindowUpdate;
+
+namespace {
+
+const net::SpdyStreamId kStreamId = 1;
+const net::SpdyStreamId kAssocStreamId = 0;
+const int32 kInitServerPushDepth = 0;
+const net::SpdyPriority kPriority = 2;
+
+class MockSpdyServerPushInterface : public mod_spdy::SpdyServerPushInterface {
+ public:
+ MOCK_METHOD4(StartServerPush,
+ mod_spdy::SpdyServerPushInterface::PushStatus(
+ net::SpdyStreamId associated_stream_id,
+ int32 server_push_depth,
+ net::SpdyPriority priority,
+ const net::SpdyNameValueBlock& request_headers));
+};
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// data frame with the given payload and FLAG_FIN setting.
+void ExpectDataFrame(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ base::StringPiece data, bool flag_fin) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsDataFrame(kStreamId, flag_fin, data));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// RST_STREAM frame with the given status code.
+void ExpectRstStream(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ net::SpdyRstStreamStatus status) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsRstStream(kStreamId, status));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// WINDOW_UPDATE frame with the given delta.
+void ExpectWindowUpdate(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ uint32 delta) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsWindowUpdate(kStreamId, delta));
+}
+
+// Expect to get a frame from the queue (within 100 milliseconds) that is a
+// WINDOW_UPDATE frame, for stream zero, with the given delta.
+void ExpectSessionWindowUpdate(mod_spdy::SpdyFramePriorityQueue* output_queue,
+ uint32 delta) {
+ net::SpdyFrameIR* raw_frame;
+ ASSERT_TRUE(output_queue->BlockingPop(
+ base::TimeDelta::FromMilliseconds(100), &raw_frame));
+ scoped_ptr<net::SpdyFrameIR> frame(raw_frame);
+ EXPECT_THAT(*frame, IsWindowUpdate(0, delta));
+}
+
+// When run, a SendDataTask sends the given data to the given stream.
+class SendDataTask : public mod_spdy::testing::AsyncTaskRunner::Task {
+ public:
+ SendDataTask(mod_spdy::SpdyStream* stream, base::StringPiece data,
+ bool flag_fin)
+ : stream_(stream), data_(data), flag_fin_(flag_fin) {}
+ virtual void Run() {
+ stream_->SendOutputDataFrame(data_, flag_fin_);
+ }
+ private:
+ mod_spdy::SpdyStream* const stream_;
+ const base::StringPiece data_;
+ const bool flag_fin_;
+ DISALLOW_COPY_AND_ASSIGN(SendDataTask);
+};
+
+// Test that the flow control features are disabled for SPDY v2.
+TEST(SpdyStreamTest, NoFlowControlInSpdy2) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ stream.SendOutputDataFrame(data, true);
+
+ // We should get all the data out in one frame anyway, because we're using
+ // SPDY v2 and the stream shouldn't be using flow control.
+ ExpectDataFrame(&output_queue, data, true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that flow control works correctly for SPDY/3.
+TEST(SpdyStreamTest, HasFlowControlInSpdy3) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 7);
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ &shared_window, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // We should get a single frame out with the first initial_window_size=10
+ // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+ ExpectDataFrame(&output_queue, "abcdefghij", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+
+ // After increasing the window size by eight, we should get eight more bytes,
+ // and then we should still be blocked.
+ stream.AdjustOutputWindowSize(8);
+ ExpectDataFrame(&output_queue, "klmnopqr", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+
+ // Finally, we increase the window size by fifteen. We should get the last
+ // eight bytes of data out (with FLAG_FIN now set), the task should be
+ // completed, and the remaining window size should be seven.
+ stream.AdjustOutputWindowSize(15);
+ ExpectDataFrame(&output_queue, "stuvwxyz", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(7, stream.current_output_window_size());
+}
+
+// Test that the session flow control window works correctly for SPDY/3.1.
+TEST(SpdyStreamTest, SessionWindowInSpdy31) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ mod_spdy::SharedFlowControlWindow shared_window(1000, 7);
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3_1, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size,
+ &output_queue, &shared_window, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // The stream window size is 10, but the session window size is only 7. So
+ // we should only get 7 bytes at first.
+ ExpectDataFrame(&output_queue, "abcdefg", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ // Now we increase the shared window size to 8. The stream window size is
+ // only 3, so we should get just 3 more bytes.
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(8));
+ ExpectDataFrame(&output_queue, "hij", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(5, shared_window.current_output_window_size());
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Next, increase the stream window by 20 bytes. The shared window is only
+ // 5, so we get 5 bytes.
+ stream.AdjustOutputWindowSize(20);
+ ExpectDataFrame(&output_queue, "klmno", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, shared_window.current_output_window_size());
+
+ // Finally, we increase the shared window size by 20. We should get the last
+ // 11 bytes of data out (with FLAG_FIN now set), and the task should be
+ // completed.
+ EXPECT_TRUE(shared_window.IncreaseOutputWindowSize(20));
+ ExpectDataFrame(&output_queue, "pqrstuvwxyz", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(9, shared_window.current_output_window_size());
+ EXPECT_EQ(4, stream.current_output_window_size());
+}
+
+// Test that flow control is well-behaved when the stream is aborted.
+TEST(SpdyStreamTest, FlowControlAbort) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 7;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // We should get a single frame out with the first initial_window_size=7
+ // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+ ExpectDataFrame(&output_queue, "abcdefg", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_FALSE(stream.is_aborted());
+
+ // We now abort with a RST_STREAM frame. We should get the RST_STREAM frame
+ // out, but no more data, and the call to SendOutputDataFrame should return
+ // even though the rest of the data was never sent.
+ stream.AbortWithRstStream(net::RST_STREAM_PROTOCOL_ERROR);
+ EXPECT_TRUE(stream.is_aborted());
+ ExpectRstStream(&output_queue, net::RST_STREAM_PROTOCOL_ERROR);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+
+ // Now that we're aborted, any attempt to send more frames should be ignored.
+ stream.SendOutputDataFrame("foobar", false);
+ net::SpdyNameValueBlock headers;
+ headers["x-foo"] = "bar";
+ stream.SendOutputHeaders(headers, true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that we abort the stream with FLOW_CONTROL_ERROR if the client
+// incorrectly overflows the 31-bit window size value.
+TEST(SpdyStreamTest, FlowControlOverflow) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, 0x60000000, &output_queue, NULL,
+ &pusher);
+
+ // Increase the window size so large that it overflows. We should get a
+ // RST_STREAM frame and the stream should be aborted.
+ EXPECT_FALSE(stream.is_aborted());
+ stream.AdjustOutputWindowSize(0x20000000);
+ EXPECT_TRUE(stream.is_aborted());
+ ExpectRstStream(&output_queue, net::RST_STREAM_FLOW_CONTROL_ERROR);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that flow control works correctly even if the window size is
+// temporarily negative.
+TEST(SpdyStreamTest, NegativeWindowSize) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Send more data than can fit in the initial window size.
+ const base::StringPiece data = "abcdefghijklmnopqrstuvwxyz";
+ mod_spdy::testing::AsyncTaskRunner runner(
+ new SendDataTask(&stream, data, true));
+ ASSERT_TRUE(runner.Start());
+
+ // We should get a single frame out with the first initial_window_size=10
+ // bytes (and no FLAG_FIN yet), and then the task should be blocked for now.
+ ExpectDataFrame(&output_queue, "abcdefghij", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Adjust the window size down (as if due to a SETTINGS frame reducing the
+ // initial window size). Our current window size should now be negative, and
+ // we should still be blocked.
+ stream.AdjustOutputWindowSize(-5);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(-5, stream.current_output_window_size());
+
+ // Adjust the initial window size up, but not enough to be positive. We
+ // should still be blocked.
+ stream.AdjustOutputWindowSize(4);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(-1, stream.current_output_window_size());
+
+ // Adjust the initial window size up again. Now we should get a few more
+ // bytes out.
+ stream.AdjustOutputWindowSize(4);
+ ExpectDataFrame(&output_queue, "klm", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectNotSet();
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Finally, open the floodgates; we should get the rest of the data.
+ stream.AdjustOutputWindowSize(800);
+ ExpectDataFrame(&output_queue, "nopqrstuvwxyz", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ runner.notification()->ExpectSetWithinMillis(100);
+ EXPECT_EQ(787, stream.current_output_window_size());
+}
+
+// Test that we handle sending empty DATA frames correctly in SPDY v2.
+TEST(SpdyStreamTest, SendEmptyDataFrameInSpdy2) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // Try to send an empty data frame without FLAG_FIN. It should be
+ // suppressed.
+ stream.SendOutputDataFrame("", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+
+ // Now send an empty data frame _with_ FLAG_FIN. It should _not_ be
+ // suppressed.
+ stream.SendOutputDataFrame("", true);
+ ExpectDataFrame(&output_queue, "", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+}
+
+// Test that we handle sending empty DATA frames correctly in SPDY v3.
+TEST(SpdyStreamTest, SendEmptyDataFrameInSpdy3) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ const int32 initial_window_size = 10;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, initial_window_size, &output_queue,
+ NULL, &pusher);
+
+ // Try to send an empty data frame without FLAG_FIN. It should be
+ // suppressed.
+ stream.SendOutputDataFrame("", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(initial_window_size, stream.current_output_window_size());
+
+ // Send one window's worth of data. It should get sent successfully.
+ const std::string data(initial_window_size, 'x');
+ stream.SendOutputDataFrame(data, false);
+ ExpectDataFrame(&output_queue, data, false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Try to send another empty data frame without FLAG_FIN. It should be
+ // suppressed, and we shouldn't block, even though the window size is zero.
+ stream.SendOutputDataFrame("", false);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(0, stream.current_output_window_size());
+
+ // Now send an empty data frame _with_ FLAG_FIN. It should _not_ be
+ // suppressed, and we still shouldn't block.
+ stream.SendOutputDataFrame("", true);
+ ExpectDataFrame(&output_queue, "", true);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(0, stream.current_output_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlInSpdy3) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // The initial window size is 64K.
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Post a SYN_STREAM frame to the input. This should not affect the input
+ // window size.
+ net::SpdyNameValueBlock request_headers;
+ request_headers[mod_spdy::http::kContentLength] = "4000";
+ request_headers[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ request_headers[mod_spdy::spdy::kSpdy3Method] = "GET";
+ request_headers[mod_spdy::spdy::kSpdy3Path] = "/index.html";
+ request_headers[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+ scoped_ptr<net::SpdySynStreamIR> syn_stream(
+ new net::SpdySynStreamIR(kStreamId));
+ syn_stream->set_associated_to_stream_id(kAssocStreamId);
+ syn_stream->set_priority(kPriority);
+ syn_stream->GetMutableNameValueBlock()->insert(
+ request_headers.begin(), request_headers.end());
+ stream.PostInputFrame(syn_stream.release());
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Send a little bit of data. This should reduce the input window size.
+ const std::string data1("abcdefghij");
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed this data. However, we shouldn't
+ // yet send a WINDOW_UPDATE frame for so small an amount, so the window size
+ // should stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Send the rest of the data. This should further reduce the input window
+ // size.
+ const std::string data2(9000, 'x');
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(kStreamId, data2));
+ data_frame->set_fin(true);
+ stream.PostInputFrame(data_frame.release());
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed a bit more of the data. However,
+ // we still shouldn't yet send a WINDOW_UPDATE frame, and the window size
+ // should still stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Now say that we've consumed a whole bunch of data. At this point, we
+ // should get a WINDOW_UPDATE frame for everything consumed so far, and the
+ // window size should increase accordingly.
+ stream.OnInputDataConsumed(8900);
+ ExpectWindowUpdate(&output_queue, 8920);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+
+ // Consume the last of the data. This is now just a little bit, so no need
+ // for a WINDOW_UPDATE here.
+ stream.OnInputDataConsumed(90);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlInSpdy31) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ mod_spdy::SharedFlowControlWindow shared_window(
+ net::kSpdyStreamInitialWindowSize,
+ net::kSpdyStreamInitialWindowSize);
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3_1, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, &shared_window, &pusher);
+
+ // The initial window size is 64K.
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Post a SYN_STREAM frame to the input. This should not affect the input
+ // window size.
+ net::SpdyHeaderBlock request_headers;
+ request_headers[mod_spdy::http::kContentLength] = "4000";
+ request_headers[mod_spdy::spdy::kSpdy3Host] = "www.example.com";
+ request_headers[mod_spdy::spdy::kSpdy3Method] = "GET";
+ request_headers[mod_spdy::spdy::kSpdy3Path] = "/index.html";
+ request_headers[mod_spdy::spdy::kSpdy3Version] = "HTTP/1.1";
+
+ scoped_ptr<net::SpdySynStreamIR> syn_stream(
+ new net::SpdySynStreamIR(kStreamId));
+ syn_stream->set_associated_to_stream_id(kAssocStreamId);
+ syn_stream->set_priority(kPriority);
+ syn_stream->GetMutableNameValueBlock()->insert(
+ request_headers.begin(), request_headers.end());
+ stream.PostInputFrame(syn_stream.release());
+ EXPECT_EQ(65536, stream.current_input_window_size());
+
+ // Send a little bit of data. This should reduce the input window size.
+ const std::string data1("abcdefghij");
+ EXPECT_TRUE(shared_window.OnReceiveInputData(data1.size()));
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed this data. However, we shouldn't
+ // yet send a WINDOW_UPDATE frame for so small an amount, so the window size
+ // should stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65526, stream.current_input_window_size());
+
+ // Send the rest of the data. This should further reduce the input window
+ // size.
+ const std::string data2(9000, 'x');
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(kStreamId, data2));
+ data_frame->set_fin(true);
+ EXPECT_TRUE(shared_window.OnReceiveInputData(data2.size()));
+ stream.PostInputFrame(data_frame.release());
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Inform the stream that we have consumed a bit more of the data. However,
+ // we still shouldn't yet send a WINDOW_UPDATE frame, and the window size
+ // should still stay the same.
+ stream.OnInputDataConsumed(10);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(56526, stream.current_input_window_size());
+
+ // Now say that we've consumed a whole bunch of data. At this point, we
+ // should get a WINDOW_UPDATE frame for everything consumed so far, and the
+ // window size should increase accordingly.
+ stream.OnInputDataConsumed(8900);
+ ExpectSessionWindowUpdate(&output_queue, 8920);
+ ExpectWindowUpdate(&output_queue, 8920);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+
+ // Consume the last of the data. This is now just a little bit, so no need
+ // for a WINDOW_UPDATE here.
+ stream.OnInputDataConsumed(90);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_EQ(65446, stream.current_input_window_size());
+}
+
+TEST(SpdyStreamTest, InputFlowControlError) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_3, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // Send a bunch of data. This should reduce the input window size.
+ const std::string data1(1000, 'x');
+ for (int i = 0; i < 65; ++i) {
+ EXPECT_EQ(65536 - i * 1000, stream.current_input_window_size());
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ }
+ EXPECT_EQ(536, stream.current_input_window_size());
+ EXPECT_FALSE(stream.is_aborted());
+
+ // Send a bit more data than there is room in the window size. This should
+ // trigger a RST_STREAM.
+ const std::string data2(537, 'y');
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data2));
+ ExpectRstStream(&output_queue, net::RST_STREAM_FLOW_CONTROL_ERROR);
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_TRUE(stream.is_aborted());
+}
+
+TEST(SpdyStreamTest, NoInputFlowControlInSpdy2) {
+ mod_spdy::SpdyFramePriorityQueue output_queue;
+ MockSpdyServerPushInterface pusher;
+ mod_spdy::SpdyStream stream(
+ mod_spdy::spdy::SPDY_VERSION_2, kStreamId, kAssocStreamId,
+ kInitServerPushDepth, kPriority, net::kSpdyStreamInitialWindowSize,
+ &output_queue, NULL, &pusher);
+
+ // Send more data than will fit in the window size. However, we shouldn't
+ // get an error, because this is SPDY/2 and there is no flow control.
+ const std::string data1(1000, 'x');
+ for (int i = 0; i < 70; ++i) {
+ stream.PostInputFrame(new net::SpdyDataIR(kStreamId, data1));
+ EXPECT_TRUE(output_queue.IsEmpty());
+ EXPECT_FALSE(stream.is_aborted());
+ }
+}
+
+} // namespace
Added: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,337 @@
+// 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/spdy_to_http_converter.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h" // for Int64ToString
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.h"
+#include "mod_spdy/common/protocol_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 {
+
+// Generate an HTTP request line from the given SPDY header block by calling
+// the OnStatusLine() method of the given visitor, and return true. If there's
+// an error, this will return false without calling any methods on the visitor.
+bool GenerateRequestLine(spdy::SpdyVersion spdy_version,
+ const net::SpdyHeaderBlock& block,
+ HttpRequestVisitorInterface* visitor) {
+ const bool spdy2 = spdy_version < spdy::SPDY_VERSION_3;
+ net::SpdyHeaderBlock::const_iterator method = block.find(
+ spdy2 ? spdy::kSpdy2Method : spdy::kSpdy3Method);
+ net::SpdyHeaderBlock::const_iterator scheme = block.find(
+ spdy2 ? spdy::kSpdy2Scheme : spdy::kSpdy3Scheme);
+ net::SpdyHeaderBlock::const_iterator host = block.find(
+ spdy2 ? http::kHost : spdy::kSpdy3Host);
+ net::SpdyHeaderBlock::const_iterator path = block.find(
+ spdy2 ? spdy::kSpdy2Url : spdy::kSpdy3Path);
+ net::SpdyHeaderBlock::const_iterator version = block.find(
+ spdy2 ? spdy::kSpdy2Version : spdy::kSpdy3Version);
+
+ if (method == block.end() ||
+ scheme == block.end() ||
+ host == block.end() ||
+ path == block.end() ||
+ version == block.end()) {
+ return false;
+ }
+
+ visitor->OnRequestLine(method->second, path->second, version->second);
+ return true;
+}
+
+// Convert the given SPDY header into HTTP header(s) by splitting on NUL bytes
+// calling the specified method (either OnLeadingHeader or OnTrailingHeader) of
+// the given visitor.
+template <void(HttpRequestVisitorInterface::*OnHeader)(
+ const base::StringPiece& key, const base::StringPiece& value)>
+void InsertHeader(const base::StringPiece key,
+ const base::StringPiece value,
+ HttpRequestVisitorInterface* visitor) {
+ // Split header values on null characters, emitting a separate
+ // header key-value pair for each substring. Logic from
+ // net/spdy/spdy_session.cc
+ for (size_t start = 0, end = 0; end != value.npos; start = end) {
+ start = value.find_first_not_of('\0', start);
+ if (start == value.npos) {
+ break;
+ }
+ end = value.find('\0', start);
+ (visitor->*OnHeader)(key, (end != value.npos ?
+ value.substr(start, (end - start)) :
+ value.substr(start)));
+ }
+}
+
+} // namespace
+
+SpdyToHttpConverter::SpdyToHttpConverter(spdy::SpdyVersion spdy_version,
+ HttpRequestVisitorInterface* visitor)
+ : spdy_version_(spdy_version),
+ visitor_(visitor),
+ state_(NO_FRAMES_YET),
+ use_chunking_(true),
+ seen_accept_encoding_(false) {
+ DCHECK_NE(spdy::SPDY_VERSION_NONE, spdy_version);
+ CHECK(visitor);
+}
+
+SpdyToHttpConverter::~SpdyToHttpConverter() {}
+
+// static
+const char* SpdyToHttpConverter::StatusString(Status status) {
+ switch (status) {
+ case SPDY_CONVERTER_SUCCESS: return "SPDY_CONVERTER_SUCCESS";
+ case FRAME_BEFORE_SYN_STREAM: return "FRAME_BEFORE_SYN_STREAM";
+ case FRAME_AFTER_FIN: return "FRAME_AFTER_FIN";
+ case EXTRA_SYN_STREAM: return "EXTRA_SYN_STREAM";
+ case INVALID_HEADER_BLOCK: return "INVALID_HEADER_BLOCK";
+ case BAD_REQUEST: return "BAD_REQUEST";
+ default:
+ LOG(DFATAL) << "Invalid status value: " << status;
+ return "???";
+ }
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertSynStreamFrame(
+ const net::SpdySynStreamIR& frame) {
+ if (state_ != NO_FRAMES_YET) {
+ return EXTRA_SYN_STREAM;
+ }
+ state_ = RECEIVED_SYN_STREAM;
+
+ const net::SpdyHeaderBlock& block = frame.name_value_block();
+
+ if (!GenerateRequestLine(spdy_version(), block, visitor_)) {
+ return BAD_REQUEST;
+ }
+
+ // Translate the headers to HTTP.
+ GenerateLeadingHeaders(block);
+
+ // If this is the last (i.e. only) frame on this stream, finish off the HTTP
+ // request.
+ if (frame.fin()) {
+ FinishRequest();
+ }
+
+ return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertHeadersFrame(
+ const net::SpdyHeadersIR& frame) {
+ if (state_ == RECEIVED_FLAG_FIN) {
+ return FRAME_AFTER_FIN;
+ } else if (state_ == NO_FRAMES_YET) {
+ return FRAME_BEFORE_SYN_STREAM;
+ }
+
+ // Parse the headers from the HEADERS frame. If there have already been any
+ // data frames, then we need to save these headers for later and send them as
+ // trailing headers. Otherwise, we can send them immediately.
+ if (state_ == RECEIVED_DATA) {
+ if (use_chunking_) {
+ const net::SpdyHeaderBlock& block = frame.name_value_block();
+ trailing_headers_.insert(block.begin(), block.end());
+ } else {
+ LOG(WARNING) << "Client sent trailing headers, "
+ << "but we had to ignore them.";
+ }
+ } else {
+ DCHECK(state_ == RECEIVED_SYN_STREAM);
+ DCHECK(trailing_headers_.empty());
+ // Translate the headers to HTTP.
+ GenerateLeadingHeaders(frame.name_value_block());
+ }
+
+ // If this is the last frame on this stream, finish off the HTTP request.
+ if (frame.fin()) {
+ FinishRequest();
+ }
+
+ return SPDY_CONVERTER_SUCCESS;
+}
+
+SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertDataFrame(
+ const net::SpdyDataIR& frame) {
+ if (state_ == RECEIVED_FLAG_FIN) {
+ return FRAME_AFTER_FIN;
+ } else if (state_ == NO_FRAMES_YET) {
+ return FRAME_BEFORE_SYN_STREAM;
+ }
+
+ // If this is the first data frame in the stream, we need to close the HTTP
+ // headers section (for streams where there are never any data frames, we
+ // close the headers section in FinishRequest instead). Just before we do,
+ // we may need to add some last-minute headers.
+ if (state_ == RECEIVED_SYN_STREAM) {
+ state_ = RECEIVED_DATA;
+
+ // Unless we're not using chunked encoding (due to having received a
+ // Content-Length headers), set Transfer-Encoding: chunked now.
+ if (use_chunking_) {
+ visitor_->OnLeadingHeader(http::kTransferEncoding, http::kChunked);
+ }
+
+ // Add any other last minute headers we need, and close the leading headers
+ // section.
+ EndOfLeadingHeaders();
+ }
+ DCHECK(state_ == RECEIVED_DATA);
+
+ // Translate the SPDY data frame into an HTTP data chunk. However, we must
+ // not emit a zero-length chunk, as that would be interpreted as the
+ // data-chunks-complete marker.
+ if (frame.data().size() > 0) {
+ if (use_chunking_) {
+ visitor_->OnDataChunk(frame.data());
+ } else {
+ visitor_->OnRawData(frame.data());
+ }
+ }
+
+ // If this is the last frame on this stream, finish off the HTTP request.
+ if (frame.fin()) {
+ FinishRequest();
+ }
+
+ return SPDY_CONVERTER_SUCCESS;
+}
+
+// Convert the given SPDY header block (e.g. from a SYN_STREAM or HEADERS
+// frame) into HTTP headers by calling OnLeadingHeader on the given visitor.
+void SpdyToHttpConverter::GenerateLeadingHeaders(
+ const net::SpdyHeaderBlock& block) {
+ for (net::SpdyHeaderBlock::const_iterator it = block.begin();
+ it != block.end(); ++it) {
+ base::StringPiece key = it->first;
+ const base::StringPiece value = it->second;
+
+ // Skip SPDY-specific (i.e. non-HTTP) headers.
+ if (spdy_version() < spdy::SPDY_VERSION_3) {
+ if (key == spdy::kSpdy2Method || key == spdy::kSpdy2Scheme ||
+ key == spdy::kSpdy2Url || key == spdy::kSpdy2Version) {
+ continue;
+ }
+ } else {
+ if (key == spdy::kSpdy3Method || key == spdy::kSpdy3Scheme ||
+ key == spdy::kSpdy3Path || key == spdy::kSpdy3Version) {
+ continue;
+ }
+ }
+
+ // Skip headers that are ignored by SPDY.
+ if (key == http::kConnection || key == http::kKeepAlive) {
+ continue;
+ }
+
+ // If the client sent a Content-Length header, take note, so that we'll
+ // know not to used chunked encoding.
+ if (key == http::kContentLength) {
+ use_chunking_ = false;
+ }
+
+ // The client shouldn't be sending us a Transfer-Encoding header; it's
+ // pretty pointless over SPDY. If they do send one, just ignore it; we may
+ // be overriding it later anyway.
+ if (key == http::kTransferEncoding) {
+ LOG(WARNING) << "Client sent \"transfer-encoding: " << value
+ << "\" header over SPDY. Why would they do that?";
+ continue;
+ }
+
+ // For SPDY v3 and later, we need to convert the SPDY ":host" header to an
+ // HTTP "host" header.
+ if (spdy_version() >= spdy::SPDY_VERSION_3 && key == spdy::kSpdy3Host) {
+ key = http::kHost;
+ }
+
+ // Take note of whether the client has sent an explicit Accept-Encoding
+ // header; if they never do, we'll insert on for them later on.
+ if (key == http::kAcceptEncoding) {
+ // TODO(mdsteele): Ideally, if the client sends something like
+ // "Accept-Encoding: lzma", we should change it to "Accept-Encoding:
+ // lzma, gzip". However, that's more work (we might need to parse the
+ // syntax, to make sure we don't naively break it), and isn't
+ // (currently) likely to come up in practice.
+ seen_accept_encoding_ = true;
+ }
+
+ InsertHeader<&HttpRequestVisitorInterface::OnLeadingHeader>(
+ key, value, visitor_);
+ }
+}
+
+void SpdyToHttpConverter::EndOfLeadingHeaders() {
+ // All SPDY clients should be assumed to support both gzip and deflate, even
+ // if they don't say so (SPDY draft 2 section 3; SPDY draft 3 section 3.2.1),
+ // and indeed some SPDY clients omit the Accept-Encoding header. So if we
+ // didn't see that header yet, add one now so that Apache knows it can use
+ // gzip/deflate.
+ if (!seen_accept_encoding_) {
+ visitor_->OnLeadingHeader(http::kAcceptEncoding, http::kGzipDeflate);
+ }
+
+ visitor_->OnLeadingHeadersComplete();
+}
+
+void SpdyToHttpConverter::FinishRequest() {
+ if (state_ == RECEIVED_DATA) {
+ if (use_chunking_) {
+ // Indicate that there is no more data coming.
+ visitor_->OnDataChunksComplete();
+
+ // Append whatever trailing headers we've buffered, if any.
+ if (!trailing_headers_.empty()) {
+ for (net::SpdyHeaderBlock::const_iterator it =
+ trailing_headers_.begin();
+ it != trailing_headers_.end(); ++it) {
+ InsertHeader<&HttpRequestVisitorInterface::OnTrailingHeader>(
+ it->first, it->second, visitor_);
+ }
+ trailing_headers_.clear();
+ visitor_->OnTrailingHeadersComplete();
+ }
+ } else {
+ // We don't add to trailing_headers_ if we're in no-chunk mode (we simply
+ // ignore trailing HEADERS frames), so trailing_headers_ should still be
+ // empty.
+ DCHECK(trailing_headers_.empty());
+ }
+ } else {
+ DCHECK(state_ == RECEIVED_SYN_STREAM);
+ // We only ever add to trailing_headers_ after receiving at least one data
+ // frame, so if we haven't received any data frames then trailing_headers_
+ // should still be empty.
+ DCHECK(trailing_headers_.empty());
+
+ // There were no data frames in this stream, so we haven't closed the
+ // normal (non-trailing) headers yet (if there had been any data frames, we
+ // would have closed the normal headers in ConvertDataFrame instead). Do
+ // so now.
+ EndOfLeadingHeaders();
+ }
+
+ // Indicate that this request is finished.
+ visitor_->OnComplete();
+ state_ = RECEIVED_FLAG_FIN;
+}
+
+} // namespace mod_spdy
Added: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,85 @@
+// Copyright 2010 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
+#define MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
+
+#include "base/basictypes.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace mod_spdy {
+
+class HttpRequestVisitorInterface;
+
+// Incrementally converts SPDY frames to HTTP streams, and passes the HTTP
+// stream to the specified HttpRequestVisitorInterface.
+class SpdyToHttpConverter {
+ public:
+ SpdyToHttpConverter(spdy::SpdyVersion spdy_version,
+ HttpRequestVisitorInterface* visitor);
+ ~SpdyToHttpConverter();
+
+ enum Status {
+ SPDY_CONVERTER_SUCCESS,
+ FRAME_BEFORE_SYN_STREAM, // first frame was not a SYN_STREAM
+ FRAME_AFTER_FIN, // received another frame after a FLAG_FIN
+ EXTRA_SYN_STREAM, // received an additional SYN_STREAM after the first
+ INVALID_HEADER_BLOCK, // the headers could not be parsed
+ BAD_REQUEST // the headers didn't constitute a valid HTTP request
+ };
+
+ static const char* StatusString(Status status);
+
+ // Return the SPDY version from which we are converting.
+ spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+
+ // Convert the SPDY frame to HTTP and make appropriate calls to the visitor.
+ // In some cases data may be buffered, but everything will get flushed out to
+ // the visitor by the time the final frame (with FLAG_FIN set) is done.
+ Status ConvertSynStreamFrame(const net::SpdySynStreamIR& frame);
+ Status ConvertHeadersFrame(const net::SpdyHeadersIR& frame);
+ Status ConvertDataFrame(const net::SpdyDataIR& frame);
+
+private:
+ // Called to generate leading headers from a SYN_STREAM or HEADERS frame.
+ void GenerateLeadingHeaders(const net::SpdyHeaderBlock& block);
+ // Called when there are no more leading headers, because we've received
+ // either data or a FLAG_FIN. This adds any last-minute needed headers
+ // before closing the leading headers section.
+ void EndOfLeadingHeaders();
+ // Called when we see a FLAG_FIN. This terminates the request and appends
+ // whatever trailing headers (if any) we have buffered.
+ void FinishRequest();
+
+ enum State {
+ NO_FRAMES_YET, // We haven't seen any frames yet.
+ RECEIVED_SYN_STREAM, // We've seen the SYN_STREAM, but no DATA yet.
+ RECEIVED_DATA, // We've seen at least one DATA frame.
+ RECEIVED_FLAG_FIN // We've seen the FLAG_FIN; no more frames allowed.
+ };
+
+ const spdy::SpdyVersion spdy_version_;
+ HttpRequestVisitorInterface* const visitor_;
+ net::SpdyHeaderBlock trailing_headers_;
+ State state_;
+ bool use_chunking_;
+ bool seen_accept_encoding_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyToHttpConverter);
+};
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_SPDY_TO_HTTP_CONVERTER_H_
Propchange: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/spdy_to_http_converter_test.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,340 @@
+// 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/spdy_to_http_converter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/common/http_request_visitor_interface.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"
+
+namespace {
+
+using mod_spdy::SpdyToHttpConverter;
+using testing::Eq;
+using testing::InSequence;
+using testing::Sequence;
+
+const char* kMethod = "GET";
+const char* kScheme = "http";
+const char* kHost = "www.example.com";
+const char* kPath = "/";
+const char* kVersion = "HTTP/1.1";
+const char kMultiValue[] = "this\0is\0\0\0four\0\0headers";
+
+class MockHttpRequestVisitor: public mod_spdy::HttpRequestVisitorInterface {
+ public:
+ MOCK_METHOD3(OnRequestLine, void(const base::StringPiece&,
+ const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD2(OnLeadingHeader, void(const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD0(OnLeadingHeadersComplete, void());
+ MOCK_METHOD1(OnRawData, void(const base::StringPiece&));
+ MOCK_METHOD1(OnDataChunk, void(const base::StringPiece&));
+ MOCK_METHOD0(OnDataChunksComplete, void());
+ MOCK_METHOD2(OnTrailingHeader, void(const base::StringPiece&,
+ const base::StringPiece&));
+ MOCK_METHOD0(OnTrailingHeadersComplete, void());
+ MOCK_METHOD0(OnComplete, void());
+};
+
+class SpdyToHttpConverterTest :
+ public testing::TestWithParam<mod_spdy::spdy::SpdyVersion> {
+ public:
+ SpdyToHttpConverterTest() : converter_(GetParam(), &visitor_) {}
+
+ protected:
+ void AddRequiredHeaders() {
+ if (converter_.spdy_version() < mod_spdy::spdy::SPDY_VERSION_3) {
+ headers_[mod_spdy::spdy::kSpdy2Method] = kMethod;
+ headers_[mod_spdy::spdy::kSpdy2Scheme] = kScheme;
+ headers_[mod_spdy::http::kHost] = kHost;
+ headers_[mod_spdy::spdy::kSpdy2Url] = kPath;
+ headers_[mod_spdy::spdy::kSpdy2Version] = kVersion;
+ } else {
+ headers_[mod_spdy::spdy::kSpdy3Method] = kMethod;
+ headers_[mod_spdy::spdy::kSpdy3Scheme] = kScheme;
+ headers_[mod_spdy::spdy::kSpdy3Host] = kHost;
+ headers_[mod_spdy::spdy::kSpdy3Path] = kPath;
+ headers_[mod_spdy::spdy::kSpdy3Version] = kVersion;
+ }
+ }
+
+ MockHttpRequestVisitor visitor_;
+ SpdyToHttpConverter converter_;
+ net::SpdyHeaderBlock headers_;
+};
+
+TEST_P(SpdyToHttpConverterTest, MultiFrameStream) {
+ // We expect all calls to happen in the specified order.
+ InSequence seq;
+
+ const net::SpdyStreamId stream_id = 1;
+ AddRequiredHeaders();
+
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("transfer-encoding"),
+ Eq("chunked")));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate)));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete());
+ scoped_ptr<net::SpdySynStreamIR> syn_stream_frame(
+ new net::SpdySynStreamIR(stream_id));
+ syn_stream_frame->set_priority(1);
+ syn_stream_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_stream_frame));
+
+ EXPECT_CALL(visitor_, OnDataChunk(Eq(kHost)));
+ scoped_ptr<net::SpdyDataIR> data_frame_1(
+ new net::SpdyDataIR(stream_id, kHost));
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame_1));
+
+ // Should be no call to OnDataChunk for an empty data frame.
+ scoped_ptr<net::SpdyDataIR> data_frame_empty(
+ new net::SpdyDataIR(stream_id, ""));
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame_empty));
+
+ EXPECT_CALL(visitor_, OnDataChunk(Eq(kVersion)));
+ EXPECT_CALL(visitor_, OnDataChunksComplete());
+ EXPECT_CALL(visitor_, OnComplete());
+ scoped_ptr<net::SpdyDataIR> data_frame_2(
+ new net::SpdyDataIR(stream_id, kVersion));
+ data_frame_2->set_fin(true);
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame_2));
+}
+
+TEST_P(SpdyToHttpConverterTest, SynFrameWithHeaders) {
+ AddRequiredHeaders();
+ headers_["foo"] = "bar";
+ headers_[mod_spdy::http::kAcceptEncoding] = "deflate, gzip, lzma";
+
+ // Create a multi-valued header to verify that it's processed
+ // properly.
+ std::string multi_values(kMultiValue, sizeof(kMultiValue));
+ headers_["multi"] = multi_values;
+
+ // Also make sure "junk" headers get skipped over.
+ headers_["empty"] = std::string("\0\0\0", 3);
+
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->set_fin(true);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ // We expect a call to OnRequestLine(), followed by several calls to
+ // OnLeadingHeader() (the order of the calls to OnLeadingHeader() is
+ // non-deterministic so we put each in its own Sequence), followed by a final
+ // call to OnLeadingHeadersComplete() and OnComplete().
+ Sequence s1, s2, s3, s4;
+ EXPECT_CALL(visitor_,
+ OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+ .InSequence(s1, s2, s3, s4);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("foo"), Eq("bar")))
+ .InSequence(s1);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq(mod_spdy::http::kAcceptEncoding),
+ Eq("deflate, gzip, lzma")))
+ .InSequence(s2);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("this")))
+ .InSequence(s3);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("is")))
+ .InSequence(s3);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("four")))
+ .InSequence(s3);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("multi"), Eq("headers")))
+ .InSequence(s3);
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+ .InSequence(s4);
+
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2, s3, s4);
+
+ EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2, s3, s4);
+
+ // Trigger the calls to the mock object by passing the frame to the
+ // converter.
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, TrailingHeaders) {
+ // First, send a SYN_STREAM frame without FLAG_FIN set. We should get the
+ // headers out that we sent, but no call yet to OnLeadingHeadersComplete,
+ // because there might still be a HEADERS frame.
+ AddRequiredHeaders();
+ headers_["foo"] = "bar";
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ Sequence s1, s2;
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+ .InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("foo"), Eq("bar")))
+ .InSequence(s1);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+ .InSequence(s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+
+ // Next, send a DATA frame. This should trigger the accept-encoding and
+ // transfer-encoding headers, and the end of the leading headers (along with
+ // the data itself, of course).
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(1, "Hello, world!\n"));
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("transfer-encoding"),
+ Eq("chunked"))).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate))).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnDataChunk(Eq("Hello, world!\n"))).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame));
+
+ // Finally, send a HEADERS frame with FLAG_FIN set. Since this is the end of
+ // the stream, we should get out a trailing header and the HTTP stream should
+ // be closed.
+ headers_.clear();
+ headers_["quux"] = "baz";
+ scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+ headers_frame->set_fin(true);
+ headers_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ EXPECT_CALL(visitor_, OnDataChunksComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnTrailingHeader(Eq("quux"), Eq("baz")))
+ .InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnTrailingHeadersComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, WithContentLength) {
+ // First, send a SYN_STREAM frame without FLAG_FIN set. We should get the
+ // headers out that we sent, but no call yet to OnLeadingHeadersComplete,
+ // because there might still be a HEADERS frame.
+ AddRequiredHeaders();
+ headers_["content-length"] = "11";
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ Sequence s1, s2;
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)))
+ .InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("content-length"), Eq("11")))
+ .InSequence(s1);
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)))
+ .InSequence(s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+
+ // Next, send a DATA frame. This should trigger the end of the leading
+ // headers (along with the data itself, of course), but because we sent a
+ // content-length, the data should not be chunked.
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(1, "foobar=quux"));
+
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate))).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete()).InSequence(s1, s2);
+ EXPECT_CALL(visitor_, OnRawData(Eq("foobar=quux"))).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertDataFrame(*data_frame));
+
+ // Finally, send a HEADERS frame with FLAG_FIN set. Since we're not chunking
+ // this stream, the trailing headers should be ignored.
+ headers_.clear();
+ headers_["x-metadata"] = "baz";
+ scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+ headers_frame->set_fin(true);
+ headers_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ EXPECT_CALL(visitor_, OnComplete()).InSequence(s1, s2);
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DoubleSynStreamFrame) {
+ AddRequiredHeaders();
+ scoped_ptr<net::SpdySynStreamIR> syn_frame(
+ new net::SpdySynStreamIR(1));
+ syn_frame->set_priority(1);
+ syn_frame->set_fin(true);
+ syn_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+
+ InSequence seq;
+ EXPECT_CALL(visitor_, OnRequestLine(Eq(kMethod), Eq(kPath), Eq(kVersion)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(Eq("host"), Eq(kHost)));
+ EXPECT_CALL(visitor_, OnLeadingHeader(
+ Eq(mod_spdy::http::kAcceptEncoding),
+ Eq(mod_spdy::http::kGzipDeflate)));
+ EXPECT_CALL(visitor_, OnLeadingHeadersComplete());
+ EXPECT_CALL(visitor_, OnComplete());
+
+ EXPECT_EQ(SpdyToHttpConverter::SPDY_CONVERTER_SUCCESS,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+ EXPECT_EQ(SpdyToHttpConverter::EXTRA_SYN_STREAM,
+ converter_.ConvertSynStreamFrame(*syn_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, HeadersFrameBeforeSynStreamFrame) {
+ headers_["x-foo"] = "bar";
+ scoped_ptr<net::SpdyHeadersIR> headers_frame(new net::SpdyHeadersIR(1));
+ headers_frame->GetMutableNameValueBlock()->insert(
+ headers_.begin(), headers_.end());
+ EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+ converter_.ConvertHeadersFrame(*headers_frame));
+}
+
+TEST_P(SpdyToHttpConverterTest, DataFrameBeforeSynStreamFrame) {
+ scoped_ptr<net::SpdyDataIR> data_frame(
+ new net::SpdyDataIR(1, kHost));
+ EXPECT_EQ(SpdyToHttpConverter::FRAME_BEFORE_SYN_STREAM,
+ converter_.ConvertDataFrame(*data_frame));
+}
+
+// Run each test over both SPDY v2 and SPDY v3.
+INSTANTIATE_TEST_CASE_P(Spdy2And3, SpdyToHttpConverterTest, testing::Values(
+ mod_spdy::spdy::SPDY_VERSION_2, mod_spdy::spdy::SPDY_VERSION_3,
+ mod_spdy::spdy::SPDY_VERSION_3_1));
+
+} // namespace
Added: httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,73 @@
+// Copyright 2012 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/testing/async_task_runner.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/thread_pool.h"
+#include "net/instaweb/util/public/function.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+namespace {
+
+class TaskFunction : public net_instaweb::Function {
+ public:
+ TaskFunction(AsyncTaskRunner::Task* task, Notification* done)
+ : task_(task), done_(done) {}
+ virtual ~TaskFunction() {}
+ protected:
+ // net_instaweb::Function methods:
+ virtual void Run() {
+ task_->Run();
+ done_->Set();
+ }
+ virtual void Cancel() {}
+ private:
+ AsyncTaskRunner::Task* const task_;
+ Notification* const done_;
+ DISALLOW_COPY_AND_ASSIGN(TaskFunction);
+};
+
+} // namespace
+
+AsyncTaskRunner::Task::Task() {}
+
+AsyncTaskRunner::Task::~Task() {}
+
+AsyncTaskRunner::AsyncTaskRunner(Task* task)
+ : task_(task), thread_pool_(1, 1) {}
+
+AsyncTaskRunner::~AsyncTaskRunner() {}
+
+bool AsyncTaskRunner::Start() {
+ // Make sure we haven't started yet.
+ DCHECK(executor_ == NULL);
+
+ if (!thread_pool_.Start()) {
+ return false;
+ }
+ executor_.reset(thread_pool_.NewExecutor());
+ executor_->AddTask(new TaskFunction(task_.get(), ¬ification_), 0);
+ return true;
+}
+
+} // namespace testing
+
+} // namespace mod_spdy
Added: httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,81 @@
+// Copyright 2012 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.
+
+#ifndef MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
+#define MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "mod_spdy/common/thread_pool.h"
+
+namespace mod_spdy {
+
+class Executor;
+
+namespace testing {
+
+// An AsyncTaskRunner is a testing utility class to make it very easy to run a
+// single piece of code concurrently, in order to write tests for concurrency
+// features of other classes. Standard usage is:
+//
+// class FooTask : public AsyncTaskRunner::Task { ... };
+//
+// AsyncTaskRunner runner(new FooTask(...));
+// ASSERT_TRUE(runner.Start());
+// ... stuff goes here ...
+// runner.notification()->ExpectSetWithin(...); // or whatever
+//
+// Note that the implementation of this class is not particularly efficient,
+// and is suitable only for testing purposes.
+class AsyncTaskRunner {
+ public:
+ // A closure to be run by the AsyncTaskRunner. If we had a simple closure
+ // class available already, we'd use that instead.
+ class Task {
+ public:
+ Task();
+ virtual ~Task();
+ virtual void Run() = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Task);
+ };
+
+ // Construct an AsyncTaskRunner that will run the given task once started.
+ // The AsyncTaskRunner gains ownership of the task.
+ explicit AsyncTaskRunner(Task* task);
+
+ ~AsyncTaskRunner();
+
+ // Start the task running and return true. If this fails, it returns false,
+ // and the test should be aborted.
+ bool Start();
+
+ // Get the notification that will be set when the task completes.
+ Notification* notification() { return ¬ification_; }
+
+ private:
+ const scoped_ptr<Task> task_;
+ mod_spdy::ThreadPool thread_pool_;
+ scoped_ptr<Executor> executor_;
+ Notification notification_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncTaskRunner);
+};
+
+} // namespace testing
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_TESTING_ASYNC_TASK_RUNNER_H_
Propchange: httpd/httpd/trunk/modules/spdy/common/testing/async_task_runner.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/httpd/trunk/modules/spdy/common/testing/notification.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/notification.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/notification.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/notification.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,66 @@
+// Copyright 2012 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/testing/notification.h"
+
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+Notification::Notification() : condvar_(&lock_), is_set_(false) {}
+
+Notification::~Notification() {
+ Set();
+}
+
+void Notification::Set() {
+ base::AutoLock autolock(lock_);
+ is_set_ = true;
+ condvar_.Broadcast();
+}
+
+void Notification::Wait() {
+ base::AutoLock autolock(lock_);
+ while (!is_set_) {
+ condvar_.Wait();
+ }
+}
+
+void Notification::ExpectNotSet() {
+ base::AutoLock autolock(lock_);
+ EXPECT_FALSE(is_set_);
+}
+
+void Notification::ExpectSetWithin(const base::TimeDelta& timeout) {
+ base::AutoLock autolock(lock_);
+ const base::TimeDelta zero = base::TimeDelta();
+ base::TimeDelta time_remaining = timeout;
+ while (time_remaining > zero && !is_set_) {
+ const base::TimeTicks start = base::TimeTicks::HighResNow();
+ condvar_.TimedWait(time_remaining);
+ time_remaining -= base::TimeTicks::HighResNow() - start;
+ }
+ EXPECT_TRUE(is_set_);
+}
+
+void Notification::ExpectSetWithinMillis(int millis) {
+ ExpectSetWithin(base::TimeDelta::FromMilliseconds(millis));
+}
+
+} // namespace testing
+
+} // namespace mod_spdy
Added: httpd/httpd/trunk/modules/spdy/common/testing/notification.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/notification.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/notification.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/notification.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,59 @@
+// Copyright 2012 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.
+
+#ifndef MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
+#define MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+
+namespace base { class TimeDelta; }
+
+namespace mod_spdy {
+
+namespace testing {
+
+// A Notification allows one thread to Wait() until another thread calls Set()
+// at least once. In order to help avoid deadlock, Set() is also called by the
+// destructor.
+class Notification {
+ public:
+ Notification();
+ ~Notification();
+
+ // Set the notification.
+ void Set();
+ // Block until the notification is set.
+ void Wait();
+ // In a unit test, expect that the notification has not yet been set.
+ void ExpectNotSet();
+ // In a unit test, expect that the notification is currently set, or becomes
+ // set by another thread within the give time delta.
+ void ExpectSetWithin(const base::TimeDelta& delay);
+ void ExpectSetWithinMillis(int millis);
+
+ private:
+ base::Lock lock_;
+ base::ConditionVariable condvar_;
+ bool is_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(Notification);
+};
+
+} // namespace testing
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_COMMON_TESTING_NOTIFICATION_H_
Propchange: httpd/httpd/trunk/modules/spdy/common/testing/notification.h
------------------------------------------------------------------------------
svn:eol-style = native
Added: httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,271 @@
+// Copyright 2012 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/testing/spdy_frame_matchers.h"
+
+#include <iostream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/stringprintf.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+void AppendHeadersString(const net::SpdyNameValueBlock& headers,
+ std::string* out) {
+ out->append("{ ");
+ bool comma = false;
+ for (net::SpdyNameValueBlock::const_iterator iter = headers.begin();
+ iter != headers.end(); ++iter) {
+ if (comma) {
+ out->append(", ");
+ }
+ base::StringAppendF(out, "'%s': '%s'", iter->first.c_str(),
+ iter->second.c_str());
+ comma = true;
+ }
+ out->append(" }");
+}
+
+class FrameToStringVisitor : public net::SpdyFrameVisitor {
+ public:
+ explicit FrameToStringVisitor(std::string* out)
+ : out_(out) {
+ CHECK(out_);
+ }
+ virtual ~FrameToStringVisitor() {}
+
+ virtual void VisitSynStream(const net::SpdySynStreamIR& syn_stream) {
+ // TODO(mdsteele): include other fields
+ base::StringAppendF(
+ out_, "SYN_STREAM(%u p%u%s%s)",
+ static_cast<unsigned>(syn_stream.stream_id()),
+ static_cast<unsigned>(syn_stream.priority()),
+ (syn_stream.fin() ? " fin" : ""),
+ (syn_stream.unidirectional() ? " unidirectional" : ""));
+ AppendHeadersString(syn_stream.name_value_block(), out_);
+ }
+ virtual void VisitSynReply(const net::SpdySynReplyIR& syn_reply) {
+ base::StringAppendF(
+ out_, "SYN_REPLY(%u%s)",
+ static_cast<unsigned>(syn_reply.stream_id()),
+ (syn_reply.fin() ? " fin" : ""));
+ AppendHeadersString(syn_reply.name_value_block(), out_);
+ }
+ virtual void VisitRstStream(const net::SpdyRstStreamIR& rst_stream) {
+ base::StringAppendF(
+ out_, "RST_STREAM(%u %s)",
+ static_cast<unsigned>(rst_stream.stream_id()),
+ mod_spdy::RstStreamStatusCodeToString(rst_stream.status()));
+ }
+ virtual void VisitSettings(const net::SpdySettingsIR& settings) {
+ base::StringAppendF(
+ out_, "SETTINGS(%s",
+ (settings.clear_settings() ? "clear " : ""));
+ bool comma = false;
+ for (net::SpdySettingsIR::ValueMap::const_iterator iter =
+ settings.values().begin(), end = settings.values().end();
+ iter != end; ++iter) {
+ if (comma) {
+ out_->append(", ");
+ }
+ base::StringAppendF(
+ out_, "%s%s%s: %d",
+ (iter->second.persist_value ? "persist " : ""),
+ (iter->second.persisted ? "persisted " : ""),
+ mod_spdy::SettingsIdToString(iter->first),
+ static_cast<int>(iter->second.value));
+ }
+ out_->append(")");
+ }
+ virtual void VisitPing(const net::SpdyPingIR& ping) {
+ base::StringAppendF(
+ out_, "PING(%u)", static_cast<unsigned>(ping.id()));
+ }
+ virtual void VisitGoAway(const net::SpdyGoAwayIR& goaway) {
+ base::StringAppendF(
+ out_, "GOAWAY(%u %s)",
+ static_cast<unsigned>(goaway.last_good_stream_id()),
+ mod_spdy::GoAwayStatusCodeToString(goaway.status()));
+ }
+ virtual void VisitHeaders(const net::SpdyHeadersIR& headers) {
+ base::StringAppendF(
+ out_, "HEADERS(%u%s)", static_cast<unsigned>(headers.stream_id()),
+ (headers.fin() ? " fin" : ""));
+ AppendHeadersString(headers.name_value_block(), out_);
+ }
+ virtual void VisitWindowUpdate(const net::SpdyWindowUpdateIR& window) {
+ base::StringAppendF(
+ out_, "WINDOW_UPDATE(%u %+d)",
+ static_cast<unsigned>(window.stream_id()),
+ static_cast<int>(window.delta()));
+ }
+ virtual void VisitCredential(const net::SpdyCredentialIR& credential) {
+ // TODO(mdsteele): include other fields
+ base::StringAppendF(
+ out_, "CREDENTIAL(%d)", static_cast<int>(credential.slot()));
+ }
+ virtual void VisitBlocked(const net::SpdyBlockedIR& blocked) {
+ base::StringAppendF(
+ out_, "BLOCKED(%u)", static_cast<unsigned>(blocked.stream_id()));
+ }
+ virtual void VisitPushPromise(const net::SpdyPushPromiseIR& push_promise) {
+ base::StringAppendF(
+ out_, "PUSH_PROMISE(%u, %u)",
+ static_cast<unsigned>(push_promise.stream_id()),
+ static_cast<unsigned>(push_promise.promised_stream_id()));
+ }
+ virtual void VisitData(const net::SpdyDataIR& data) {
+ base::StringAppendF(
+ out_, "DATA(%u%s \"", static_cast<unsigned>(data.stream_id()),
+ (data.fin() ? " fin" : ""));
+ out_->append(data.data().data(), data.data().size());
+ out_->append("\")");
+ }
+
+ private:
+ std::string* out_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameToStringVisitor);
+};
+
+void AppendSpdyFrameToString(const net::SpdyFrameIR& frame, std::string* out) {
+ FrameToStringVisitor visitor(out);
+ frame.Visit(&visitor);
+}
+
+class IsEquivalentFrameMatcher :
+ public ::testing::MatcherInterface<const net::SpdyFrameIR&> {
+ public:
+ explicit IsEquivalentFrameMatcher(const net::SpdyFrameIR& frame);
+ virtual ~IsEquivalentFrameMatcher();
+ virtual bool MatchAndExplain(const net::SpdyFrameIR& frame,
+ ::testing::MatchResultListener* listener) const;
+ virtual void DescribeTo(std::ostream* out) const;
+ virtual void DescribeNegationTo(std::ostream* out) const;
+
+ private:
+ std::string expected_;
+
+ DISALLOW_COPY_AND_ASSIGN(IsEquivalentFrameMatcher);
+};
+
+IsEquivalentFrameMatcher::IsEquivalentFrameMatcher(
+ const net::SpdyFrameIR& frame) {
+ AppendSpdyFrameToString(frame, &expected_);
+}
+
+IsEquivalentFrameMatcher::~IsEquivalentFrameMatcher() {}
+
+bool IsEquivalentFrameMatcher::MatchAndExplain(
+ const net::SpdyFrameIR& frame,
+ ::testing::MatchResultListener* listener) const {
+ std::string actual;
+ AppendSpdyFrameToString(frame, &actual);
+ if (actual != expected_) {
+ *listener << "is a " << actual << " frame";
+ return false;
+ }
+ return true;
+}
+
+void IsEquivalentFrameMatcher::DescribeTo(std::ostream* out) const {
+ *out << "is a " << expected_ << " frame";
+}
+
+void IsEquivalentFrameMatcher::DescribeNegationTo(std::ostream* out) const {
+ *out << "isn't a " << expected_ << " frame";
+}
+
+} // namespace
+
+namespace mod_spdy {
+
+namespace testing {
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSynStream(
+ net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+ net::SpdyPriority priority, bool fin, bool unidirectional,
+ const net::SpdyNameValueBlock& headers) {
+ net::SpdySynStreamIR frame(stream_id);
+ frame.set_associated_to_stream_id(assoc_stream_id);
+ frame.set_priority(priority);
+ frame.set_fin(fin);
+ frame.set_unidirectional(unidirectional);
+ frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSynReply(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers) {
+ net::SpdySynReplyIR frame(stream_id);
+ frame.set_fin(fin);
+ frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsRstStream(
+ net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status) {
+ net::SpdyRstStreamIR frame(stream_id, status);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsSettings(
+ net::SpdySettingsIds id, int32 value) {
+ net::SpdySettingsIR frame;
+ frame.AddSetting(id, false, false, value);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsPing(net::SpdyPingId ping_id) {
+ net::SpdyPingIR frame(ping_id);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsGoAway(
+ net::SpdyStreamId last_good_stream_id, net::SpdyGoAwayStatus status) {
+ net::SpdyGoAwayIR frame(last_good_stream_id, status);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsHeaders(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers) {
+ net::SpdyHeadersIR frame(stream_id);
+ frame.set_fin(fin);
+ frame.GetMutableNameValueBlock()->insert(headers.begin(), headers.end());
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsWindowUpdate(
+ net::SpdyStreamId stream_id, uint32 delta) {
+ net::SpdyWindowUpdateIR frame(stream_id, delta);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+::testing::Matcher<const net::SpdyFrameIR&> IsDataFrame(
+ net::SpdyStreamId stream_id, bool fin, base::StringPiece payload) {
+ net::SpdyDataIR frame(stream_id, payload);
+ frame.set_fin(fin);
+ return ::testing::MakeMatcher(new IsEquivalentFrameMatcher(frame));
+}
+
+} // namespace testing
+
+} // namespace mod_spdy
Added: httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,79 @@
+// Copyright 2012 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.
+
+#ifndef MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
+#define MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace mod_spdy {
+
+namespace testing {
+
+// Make a matcher that requires the argument to be a SYN_STREAM frame with the
+// given stream ID, associated stream ID, priority, flag_fin,
+// flag_unidirectional, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsSynStream(
+ net::SpdyStreamId stream_id, net::SpdyStreamId assoc_stream_id,
+ net::SpdyPriority priority, bool fin, bool unidirectional,
+ const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a SYN_REPLY frame with the
+// given stream ID, flag_fin, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsSynReply(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a RST_STREAM frame with the
+// given stream ID and status code.
+::testing::Matcher<const net::SpdyFrameIR&> IsRstStream(
+ net::SpdyStreamId stream_id, net::SpdyRstStreamStatus status);
+
+// Make a matcher that requires the argument to be a SETTINGS frame with the
+// given setting.
+::testing::Matcher<const net::SpdyFrameIR&> IsSettings(
+ net::SpdySettingsIds id, int32 value);
+
+// Make a matcher that requires the argument to be a PING frame with the
+// given ID.
+::testing::Matcher<const net::SpdyFrameIR&> IsPing(net::SpdyPingId ping_id);
+
+// Make a matcher that requires the argument to be a GOAWAY frame with the
+// given last-good-stream-ID and status code.
+::testing::Matcher<const net::SpdyFrameIR&> IsGoAway(
+ net::SpdyStreamId last_good_stream_id, net::SpdyGoAwayStatus status);
+
+// Make a matcher that requires the argument to be a HEADERS frame with the
+// given stream ID, flag_fin, and headers.
+::testing::Matcher<const net::SpdyFrameIR&> IsHeaders(
+ net::SpdyStreamId stream_id, bool fin,
+ const net::SpdyNameValueBlock& headers);
+
+// Make a matcher that requires the argument to be a WINDOW_UPDATE frame with
+// the given window-size-delta.
+::testing::Matcher<const net::SpdyFrameIR&> IsWindowUpdate(
+ net::SpdyStreamId stream_id, uint32 delta);
+
+// Make a matcher that requires the argument to be a DATA frame.
+::testing::Matcher<const net::SpdyFrameIR&> IsDataFrame(
+ net::SpdyStreamId stream_id, bool fin, base::StringPiece payload);
+
+} // namespace testing
+
+} // namespace mod_spdy
+
+#endif // MOD_SPDY_TESTING_SPDY_FRAME_MATCHERS_H_
Propchange: httpd/httpd/trunk/modules/spdy/common/testing/spdy_frame_matchers.h
------------------------------------------------------------------------------
svn:eol-style = native