httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject svn commit: r1591620 [9/14] - in /httpd/mod_spdy/branches/httpd-2.2.x: ./ base/ base/metrics/ build/ install/ install/common/ install/debian/ install/rpm/ mod_spdy/ mod_spdy/apache/ mod_spdy/apache/filters/ mod_spdy/apache/testing/ mod_spdy/common/ mod...
Date Thu, 01 May 2014 11:39:32 GMT
Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream.h Thu May  1 11:39:27 2014
@@ -0,0 +1,187 @@
+// 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_COMMON_SPDY_STREAM_H_
+#define MOD_SPDY_COMMON_SPDY_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/spdy/spdy_protocol.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_frame_queue.h"
+#include "mod_spdy/common/spdy_server_push_interface.h"
+
+namespace mod_spdy {
+
+class SharedFlowControlWindow;
+class SpdyFramePriorityQueue;
+
+// Represents one stream of a SPDY connection.  This class is used to
+// coordinate and pass SPDY frames between the SPDY-to-HTTP filter, the
+// HTTP-to-SPDY filter, and the master SPDY connection thread.  This class is
+// thread-safe, and in particular can be used concurrently by the stream thread
+// and the connection thread (although certain methods are meant to only ever
+// be called by one thread or the other; see the doc comments).
+class SpdyStream {
+ public:
+  // The SpdyStream object does *not* take ownership of any of these arguments.
+  SpdyStream(spdy::SpdyVersion spdy_version,
+             net::SpdyStreamId stream_id,
+             net::SpdyStreamId associated_stream_id_,
+             int32 server_push_depth,
+             net::SpdyPriority priority,
+             int32 initial_output_window_size,
+             SpdyFramePriorityQueue* output_queue,
+             SharedFlowControlWindow* shared_window,
+             SpdyServerPushInterface* pusher);
+  ~SpdyStream();
+
+  // What version of SPDY is being used for this connection?
+  spdy::SpdyVersion spdy_version() const { return spdy_version_; }
+
+  // Return true if this stream was initiated by the server, false if it was
+  // initiated by the client.
+  bool is_server_push() const;
+
+  // Get the ID for this SPDY stream.
+  net::SpdyStreamId stream_id() const { return stream_id_; }
+
+  // Get the ID for the SPDY stream with which this one is associated.  By the
+  // SPDY spec, if there is no associated stream, this will be zero.
+  net::SpdyStreamId associated_stream_id() const {
+    return associated_stream_id_;
+  }
+
+  // Get the current depth of the stream. 0 if this is a client initiated
+  // stream or associated_stream.depth+1 if this stream was created as
+  // a result of another stream.
+  int32 server_push_depth() const { return server_push_depth_; }
+
+  // Get the priority of this stream.
+  net::SpdyPriority priority() const { return priority_; }
+
+  // Return true if this stream has been aborted and should shut down.
+  bool is_aborted() const;
+
+  // Abort this stream.  This method returns immediately, and the thread
+  // running the stream will stop as soon as possible (if it is currently
+  // blocked on the window size, it will be woken up).
+  void AbortSilently();
+
+  // Same as AbortSilently, but also sends a RST_STREAM frame for this stream.
+  void AbortWithRstStream(net::SpdyRstStreamStatus status);
+
+  // What are the current window sizes for this stream?  These are mostly
+  // useful for debugging.  Requires that spdy_version() >= SPDY_VERSION_3.
+  int32 current_input_window_size() const;
+  int32 current_output_window_size() const;
+
+  // This should be called by the stream thread for each chunk of input data
+  // that it consumes.  The SpdyStream object will take care of sending
+  // WINDOW_UPDATE frames as appropriate (automatically bunching up smaller,
+  // chunks to avoid sending too many frames, and of course not sending
+  // WINDOW_UPDATE frames for SPDY/2 connections).  The connection thread must
+  // not call this method.
+  void OnInputDataConsumed(size_t size);
+
+  // This should be called by the connection thread to adjust the window size,
+  // either due to receiving a WINDOW_UPDATE frame from the client, or from the
+  // client changing the initial window size with a SETTINGS frame.  The delta
+  // argument will usually be positive (WINDOW_UPDATE is always positive), but
+  // *can* be negative (if the client reduces the window size with SETTINGS).
+  //
+  // This method should *not* be called by the stream thread; the SpdyStream
+  // object will automatically take care of decreasing the window size for sent
+  // data.
+  void AdjustOutputWindowSize(int32 delta);
+
+  // Provide a SPDY frame sent from the client.  This is to be called from the
+  // master connection thread.  This method takes ownership of the frame
+  // object.
+  void PostInputFrame(net::SpdyFrameIR* frame);
+
+  // Get a SPDY frame from the client and return true, or return false if no
+  // frame is available.  If the block argument is true and no frame is
+  // currently available, block until a frame becomes available or the stream
+  // is aborted.  This is to be called from the stream thread.  The caller
+  // gains ownership of the provided frame.
+  bool GetInputFrame(bool block, net::SpdyFrameIR** frame);
+
+  // Send a SYN_STREAM frame to the client for this stream.  This may only be
+  // called if is_server_push() is true.
+  void SendOutputSynStream(const net::SpdyHeaderBlock& headers, bool flag_fin);
+
+  // Send a SYN_REPLY frame to the client for this stream.  This may only be
+  // called if is_server_push() is false.
+  void SendOutputSynReply(const net::SpdyHeaderBlock& headers, bool flag_fin);
+
+  // Send a HEADERS frame to the client for this stream.
+  void SendOutputHeaders(const net::SpdyHeaderBlock& headers, bool flag_fin);
+
+  // Send a SPDY data frame to the client on this stream.
+  void SendOutputDataFrame(base::StringPiece data, bool flag_fin);
+
+  // Initiate a SPDY server push associated with this stream, 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.  Requires that spdy_version() >= 3.
+  SpdyServerPushInterface::PushStatus StartServerPush(
+      net::SpdyPriority priority,
+      const net::SpdyHeaderBlock& request_headers);
+
+ private:
+  // Send a SPDY frame to the client.  This is to be called from the stream
+  // thread.  This method takes ownership of the frame object.  Must be holding
+  // lock_ to call this method.
+  void SendOutputFrame(net::SpdyFrameIR* frame);
+
+  // Aborts the input queue, sets aborted_, and wakes up threads waiting on
+  // condvar_.  Must be holding lock_ to call this method.
+  void InternalAbortSilently();
+
+  // Like InternalAbortSilently, but also sends a RST_STREAM frame for this
+  // stream.  Must be holding lock_ to call this method.
+  void InternalAbortWithRstStream(net::SpdyRstStreamStatus status);
+
+  // These fields are all either constant or thread-safe, and do not require
+  // additional synchronization.
+  const spdy::SpdyVersion spdy_version_;
+  const net::SpdyStreamId stream_id_;
+  const net::SpdyStreamId associated_stream_id_;
+  const int32 server_push_depth_;
+  const net::SpdyPriority priority_;
+  SpdyFrameQueue input_queue_;
+  SpdyFramePriorityQueue* const output_queue_;
+  SharedFlowControlWindow* const shared_window_;
+  SpdyServerPushInterface* const pusher_;
+
+  // The lock protects the fields below.  The above fields do not require
+  // additional synchronization.
+  mutable base::Lock lock_;
+  base::ConditionVariable condvar_;
+  bool aborted_;
+  int32 output_window_size_;
+  int32 input_window_size_;
+  size_t input_bytes_consumed_;  // consumed since we last sent a WINDOW_UPDATE
+  size_t input_bytes_unconsumed_;  // received but not yet consumed
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyStream);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SPDY_STREAM_H_

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

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.cc Thu May  1 11:39:27 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_stream_task_factory.h"
+
+namespace mod_spdy {
+
+SpdyStreamTaskFactory::SpdyStreamTaskFactory() {}
+
+SpdyStreamTaskFactory::~SpdyStreamTaskFactory() {}
+
+}  // namespace mod_spdy

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.h
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.h?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.h (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_stream_task_factory.h Thu May  1 11:39:27 2014
@@ -0,0 +1,49 @@
+// 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_STREAM_TASK_FACTORY_H_
+#define MOD_SPDY_COMMON_SPDY_STREAM_TASK_FACTORY_H_
+
+#include "base/basictypes.h"
+
+namespace net_instaweb { class Function; }
+
+namespace mod_spdy {
+
+class SpdyStream;
+
+// SpdyStreamTaskFactory is a helper interface for the SpdySession class.
+// The task factory generates tasks that take care of processing SPDY streams.
+// The task factory must not be deleted before all such tasks have been
+// disposed of (run or cancelled).
+class SpdyStreamTaskFactory {
+ public:
+  SpdyStreamTaskFactory();
+  virtual ~SpdyStreamTaskFactory();
+
+  // Create a new task to process the given stream.  Running the task should
+  // process the stream -- that is, pull frames off the stream's input queue
+  // and post frames to the stream's output queue -- and the task should not
+  // complete until the stream is completely finished.
+  //
+  // The implementation may assume that the factory will outlive the task.
+  virtual net_instaweb::Function* NewStreamTask(SpdyStream* stream) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SpdyStreamTaskFactory);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_SPDY_STREAM_TASK_FACTORY_H_

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

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

Added: httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_to_http_converter_test.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_to_http_converter_test.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_to_http_converter_test.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/spdy_to_http_converter_test.cc Thu May  1 11:39:27 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/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/testing/async_task_runner.cc
URL: http://svn.apache.org/viewvc/httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/testing/async_task_runner.cc?rev=1591620&view=auto
==============================================================================
--- httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/testing/async_task_runner.cc (added)
+++ httpd/mod_spdy/branches/httpd-2.2.x/mod_spdy/common/testing/async_task_runner.cc Thu May  1 11:39:27 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(), &notification_), 0);
+  return true;
+}
+
+}  // namespace testing
+
+}  // namespace mod_spdy

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

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



Mime
View raw message