Adds a Http2VisitorInterface method for when the library wants to send serialized frame data to the peer.
This replaces Http2Adapter's GetBytesToWrite(), and will make compatibility with existing usage a little easier.
PiperOrigin-RevId: 375197346
diff --git a/http2/adapter/callback_visitor.cc b/http2/adapter/callback_visitor.cc
index eaf4b61..8baba58 100644
--- a/http2/adapter/callback_visitor.cc
+++ b/http2/adapter/callback_visitor.cc
@@ -36,6 +36,11 @@
namespace http2 {
namespace adapter {
+ssize_t CallbackVisitor::OnReadyToSend(absl::string_view serialized) {
+ return callbacks_->send_callback(nullptr, ToUint8Ptr(serialized.data()),
+ serialized.size(), 0, user_data_);
+}
+
void CallbackVisitor::OnConnectionError() {
QUICHE_LOG(FATAL) << "Not implemented";
}
diff --git a/http2/adapter/callback_visitor.h b/http2/adapter/callback_visitor.h
index 94746bf..5516008 100644
--- a/http2/adapter/callback_visitor.h
+++ b/http2/adapter/callback_visitor.h
@@ -23,6 +23,7 @@
callbacks_(std::move(callbacks)),
user_data_(user_data) {}
+ ssize_t OnReadyToSend(absl::string_view serialized) override;
void OnConnectionError() override;
void OnFrameHeader(Http2StreamId stream_id,
size_t length,
diff --git a/http2/adapter/http2_adapter.h b/http2/adapter/http2_adapter.h
index c97fd27..c18f1bb 100644
--- a/http2/adapter/http2_adapter.h
+++ b/http2/adapter/http2_adapter.h
@@ -65,10 +65,8 @@
// END_METADATA flag set.
virtual void SubmitMetadata(Http2StreamId stream_id, bool fin) = 0;
- // Returns serialized bytes for writing to the wire.
- // Writes should be submitted to Http2Adapter first, so that Http2Adapter
- // has data to serialize and return in this method.
- virtual std::string GetBytesToWrite(absl::optional<size_t> max_bytes) = 0;
+ // Invokes the visitor's OnReadyToSend() method for serialized frame data.
+ virtual void Send() = 0;
// Returns the connection-level flow control window for the peer.
virtual int GetPeerConnectionWindow() const = 0;
diff --git a/http2/adapter/http2_visitor_interface.h b/http2/adapter/http2_visitor_interface.h
index 12729e7..a7e34e9 100644
--- a/http2/adapter/http2_visitor_interface.h
+++ b/http2/adapter/http2_visitor_interface.h
@@ -50,6 +50,11 @@
Http2VisitorInterface& operator=(const Http2VisitorInterface&) = delete;
virtual ~Http2VisitorInterface() = default;
+ // Called when there are serialized frames to send. Should return how many
+ // bytes were actually sent. Returning 0 indicates that sending is blocked.
+ // Returning -1 indicates an error.
+ virtual ssize_t OnReadyToSend(absl::string_view serialized) = 0;
+
// Called when a connection-level processing error has been encountered.
virtual void OnConnectionError() = 0;
diff --git a/http2/adapter/mock_http2_visitor.h b/http2/adapter/mock_http2_visitor.h
index 171d40f..3c4ee2f 100644
--- a/http2/adapter/mock_http2_visitor.h
+++ b/http2/adapter/mock_http2_visitor.h
@@ -13,6 +13,10 @@
public:
MockHttp2Visitor() = default;
+ MOCK_METHOD(ssize_t,
+ OnReadyToSend,
+ (absl::string_view serialized),
+ (override));
MOCK_METHOD(void, OnConnectionError, (), (override));
MOCK_METHOD(
void,
diff --git a/http2/adapter/nghttp2_adapter.cc b/http2/adapter/nghttp2_adapter.cc
index 878c040..f9fe6fe 100644
--- a/http2/adapter/nghttp2_adapter.cc
+++ b/http2/adapter/nghttp2_adapter.cc
@@ -84,21 +84,11 @@
QUICHE_LOG(DFATAL) << "Not implemented";
}
-std::string NgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) {
- ssize_t num_bytes = 0;
- std::string result;
- do {
- const uint8_t* data = nullptr;
- num_bytes = nghttp2_session_mem_send(session_->raw_ptr(), &data);
- if (num_bytes > 0) {
- absl::StrAppend(
- &result,
- absl::string_view(reinterpret_cast<const char*>(data), num_bytes));
- } else if (num_bytes < 0) {
- visitor_.OnConnectionError();
- }
- } while (num_bytes > 0);
- return result;
+void NgHttp2Adapter::Send() {
+ const int result = nghttp2_session_send(session_->raw_ptr());
+ if (result != 0) {
+ visitor_.OnConnectionError();
+ }
}
int NgHttp2Adapter::GetPeerConnectionWindow() const {
diff --git a/http2/adapter/nghttp2_adapter.h b/http2/adapter/nghttp2_adapter.h
index 55f8d3d..1fa4de8 100644
--- a/http2/adapter/nghttp2_adapter.h
+++ b/http2/adapter/nghttp2_adapter.h
@@ -62,10 +62,8 @@
// END_METADATA flag set.
void SubmitMetadata(Http2StreamId stream_id, bool end_metadata) override;
- // Returns serialized bytes for writing to the wire. Writes should be
- // submitted to Nghttp2Adapter first, so that Nghttp2Adapter has data to
- // serialize and return in this method.
- std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override;
+ // Invokes the visitor's OnReadyToSend() method for serialized frame data.
+ void Send() override;
// Returns the connection-level flow control window for the peer.
int GetPeerConnectionWindow() const override;
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 3aec115..ede56f1 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -33,10 +33,12 @@
}
TEST(NgHttp2AdapterTest, ClientHandlesFrames) {
- testing::StrictMock<MockHttp2Visitor> visitor;
+ DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
- std::string serialized = adapter->GetBytesToWrite(absl::nullopt);
- EXPECT_THAT(serialized, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ adapter->Send();
+ EXPECT_THAT(visitor.data(),
+ testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ visitor.Clear();
const std::string initial_frames = TestFrameSequence()
.ServerPreface()
@@ -61,9 +63,10 @@
EXPECT_EQ(adapter->GetPeerConnectionWindow(),
kDefaultInitialStreamWindowSize + 1000);
// Some bytes should have been serialized.
- serialized = adapter->GetBytesToWrite(absl::nullopt);
- EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::PING}));
+ adapter->Send();
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING}));
+ visitor.Clear();
const std::vector<const Header> headers1 =
ToHeaders({{":method", "GET"},
@@ -104,10 +107,11 @@
ASSERT_GT(stream_id3, 0);
QUICHE_LOG(INFO) << "Created stream: " << stream_id3;
- serialized = adapter->GetBytesToWrite(absl::nullopt);
- EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS,
- spdy::SpdyFrameType::HEADERS,
- spdy::SpdyFrameType::HEADERS}));
+ adapter->Send();
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::HEADERS}));
+ visitor.Clear();
const std::string stream_frames =
TestFrameSequence()
@@ -160,8 +164,8 @@
// Client will not have anything else to write.
EXPECT_FALSE(adapter->session().want_write());
- serialized = adapter->GetBytesToWrite(absl::nullopt);
- EXPECT_THAT(serialized, testing::IsEmpty());
+ adapter->Send();
+ EXPECT_THAT(visitor.data(), testing::IsEmpty());
}
TEST(NgHttp2AdapterTest, ServerConstruction) {
@@ -173,7 +177,7 @@
}
TEST(NgHttp2AdapterTest, ServerHandlesFrames) {
- testing::StrictMock<MockHttp2Visitor> visitor;
+ DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
const std::string frames = TestFrameSequence()
@@ -242,11 +246,11 @@
EXPECT_TRUE(adapter->session().want_write());
// Some bytes should have been serialized.
- std::string serialized = adapter->GetBytesToWrite(absl::nullopt);
+ adapter->Send();
// SETTINGS ack, two PING acks.
- EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::PING,
- spdy::SpdyFrameType::PING}));
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING,
+ spdy::SpdyFrameType::PING}));
}
} // namespace
diff --git a/http2/adapter/nghttp2_callbacks.cc b/http2/adapter/nghttp2_callbacks.cc
index 8774873..c3e1dc8 100644
--- a/http2/adapter/nghttp2_callbacks.cc
+++ b/http2/adapter/nghttp2_callbacks.cc
@@ -16,6 +16,22 @@
namespace adapter {
namespace callbacks {
+ssize_t OnReadyToSend(nghttp2_session* /* session */,
+ const uint8_t* data,
+ size_t length,
+ int flags,
+ void* user_data) {
+ auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+ const ssize_t result = visitor->OnReadyToSend(ToStringView(data, length));
+ if (result < 0) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ } else if (result == 0) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ } else {
+ return result;
+ }
+}
+
int OnBeginFrame(nghttp2_session* /* session */,
const nghttp2_frame_hd* header,
void* user_data) {
@@ -190,6 +206,7 @@
nghttp2_session_callbacks* callbacks;
nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_send_callback(callbacks, &OnReadyToSend);
nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks,
&OnBeginFrame);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
diff --git a/http2/adapter/nghttp2_callbacks.h b/http2/adapter/nghttp2_callbacks.h
index 5fbaee0..f4621c1 100644
--- a/http2/adapter/nghttp2_callbacks.h
+++ b/http2/adapter/nghttp2_callbacks.h
@@ -13,6 +13,13 @@
// beginning of its lifetime. It is expected that |user_data| holds an
// Http2VisitorInterface.
+// Callback once the library is ready to send serialized frames.
+ssize_t OnReadyToSend(nghttp2_session* session,
+ const uint8_t* data,
+ size_t length,
+ int flags,
+ void* user_data);
+
// Callback once a frame header has been received.
int OnBeginFrame(nghttp2_session* session, const nghttp2_frame_hd* header,
void* user_data);
diff --git a/http2/adapter/nghttp2_session_test.cc b/http2/adapter/nghttp2_session_test.cc
index 169d2f2..5e0fd23 100644
--- a/http2/adapter/nghttp2_session_test.cc
+++ b/http2/adapter/nghttp2_session_test.cc
@@ -26,16 +26,6 @@
WINDOW_UPDATE,
};
-ssize_t SaveSessionOutput(nghttp2_session* /* session*/,
- const uint8_t* data,
- size_t length,
- int /* flags */,
- void* user_data) {
- auto visitor = static_cast<DataSavingVisitor*>(user_data);
- visitor->Save(ToStringView(data, length));
- return length;
-}
-
class NgHttp2SessionTest : public testing::Test {
public:
nghttp2_option* CreateOptions() {
@@ -47,8 +37,6 @@
nghttp2_session_callbacks_unique_ptr CreateCallbacks() {
nghttp2_session_callbacks_unique_ptr callbacks = callbacks::Create();
- nghttp2_session_callbacks_set_send_callback(callbacks.get(),
- &SaveSessionOutput);
return callbacks;
}
diff --git a/http2/adapter/oghttp2_adapter.cc b/http2/adapter/oghttp2_adapter.cc
index 9f287fa..788310a 100644
--- a/http2/adapter/oghttp2_adapter.cc
+++ b/http2/adapter/oghttp2_adapter.cc
@@ -71,8 +71,8 @@
QUICHE_BUG(oghttp2_submit_metadata) << "Not implemented";
}
-std::string OgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) {
- return session_->GetBytesToWrite(max_bytes);
+void OgHttp2Adapter::Send() {
+ session_->Send();
}
int OgHttp2Adapter::GetPeerConnectionWindow() const {
diff --git a/http2/adapter/oghttp2_adapter.h b/http2/adapter/oghttp2_adapter.h
index 572ba04..90b4391 100644
--- a/http2/adapter/oghttp2_adapter.h
+++ b/http2/adapter/oghttp2_adapter.h
@@ -32,7 +32,7 @@
void SubmitWindowUpdate(Http2StreamId stream_id,
int window_increment) override;
void SubmitMetadata(Http2StreamId stream_id, bool fin) override;
- std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override;
+ void Send() override;
int GetPeerConnectionWindow() const override;
void MarkDataConsumedForStream(Http2StreamId stream_id,
size_t num_bytes) override;
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 4c849f6..0f185d2 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -18,7 +18,7 @@
adapter_ = OgHttp2Adapter::Create(http2_visitor_, options);
}
- testing::StrictMock<MockHttp2Visitor> http2_visitor_;
+ DataSavingVisitor http2_visitor_;
std::unique_ptr<OgHttp2Adapter> adapter_;
};
@@ -62,8 +62,9 @@
adapter_->SubmitWindowUpdate(3, 127);
EXPECT_TRUE(adapter_->session().want_write());
+ adapter_->Send();
EXPECT_THAT(
- adapter_->GetBytesToWrite(absl::nullopt),
+ http2_visitor_.data(),
EqualsFrames(
{spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::PRIORITY,
spdy::SpdyFrameType::RST_STREAM, spdy::SpdyFrameType::PING,
@@ -80,12 +81,15 @@
adapter_->SubmitPing(42);
EXPECT_TRUE(adapter_->session().want_write());
- const std::string first_part = adapter_->GetBytesToWrite(10);
+ http2_visitor_.set_send_limit(20);
+ adapter_->Send();
EXPECT_TRUE(adapter_->session().want_write());
- const std::string second_part = adapter_->GetBytesToWrite(absl::nullopt);
+ adapter_->Send();
+ EXPECT_TRUE(adapter_->session().want_write());
+ adapter_->Send();
EXPECT_FALSE(adapter_->session().want_write());
EXPECT_THAT(
- absl::StrCat(first_part, second_part),
+ http2_visitor_.data(),
EqualsFrames({spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::GOAWAY,
spdy::SpdyFrameType::PING}));
}
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 2222fed..e45502e 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -75,20 +75,28 @@
frames_.push_back(std::move(frame));
}
-std::string OgHttp2Session::GetBytesToWrite(absl::optional<size_t> max_bytes) {
- const size_t serialized_max =
- max_bytes ? max_bytes.value() : std::numeric_limits<size_t>::max();
- std::string serialized = std::move(serialized_prefix_);
- while (serialized.size() < serialized_max && !frames_.empty()) {
+void OgHttp2Session::Send() {
+ ssize_t result = std::numeric_limits<ssize_t>::max();
+ // Flush any serialized prefix.
+ while (result > 0 && !serialized_prefix_.empty()) {
+ result = visitor_.OnReadyToSend(serialized_prefix_);
+ if (result > 0) {
+ serialized_prefix_.erase(0, result);
+ }
+ }
+ // Serialize and send frames in the queue.
+ // TODO(birenroy): wake streams for DATA frame writes
+ while (result > 0 && !frames_.empty()) {
spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frames_.front());
- absl::StrAppend(&serialized, absl::string_view(frame));
frames_.pop_front();
+ result = visitor_.OnReadyToSend(absl::string_view(frame));
+ if (result < 0) {
+ visitor_.OnConnectionError();
+ } else if (result < frame.size()) {
+ serialized_prefix_.assign(frame.data() + result, frame.size() - result);
+ break;
+ }
}
- if (serialized.size() > serialized_max) {
- serialized_prefix_ = serialized.substr(serialized_max);
- serialized.resize(serialized_max);
- }
- return serialized;
}
void OgHttp2Session::OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index b0bc28b..39f4cdc 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -28,9 +28,8 @@
// Enqueues a frame for transmission to the peer.
void EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame);
- // If |want_write()| returns true, this method will return a non-empty string
- // containing serialized HTTP/2 frames to write to the peer.
- std::string GetBytesToWrite(absl::optional<size_t> max_bytes);
+ // Invokes the visitor's OnReadyToSend() method for serialized frame data.
+ void Send();
// From Http2Session.
ssize_t ProcessBytes(absl::string_view bytes) override;
diff --git a/http2/adapter/recording_http2_visitor.cc b/http2/adapter/recording_http2_visitor.cc
index 78ec423..4cae3c0 100644
--- a/http2/adapter/recording_http2_visitor.cc
+++ b/http2/adapter/recording_http2_visitor.cc
@@ -7,6 +7,11 @@
namespace adapter {
namespace test {
+ssize_t RecordingHttp2Visitor::OnReadyToSend(absl::string_view serialized) {
+ events_.push_back(absl::StrFormat("OnReadyToSend %d", serialized.size()));
+ return serialized.size();
+}
+
void RecordingHttp2Visitor::OnConnectionError() {
events_.push_back("OnConnectionError");
}
diff --git a/http2/adapter/recording_http2_visitor.h b/http2/adapter/recording_http2_visitor.h
index 452b451..feac2de 100644
--- a/http2/adapter/recording_http2_visitor.h
+++ b/http2/adapter/recording_http2_visitor.h
@@ -18,6 +18,7 @@
using EventSequence = std::list<Event>;
// From Http2VisitorInterface
+ ssize_t OnReadyToSend(absl::string_view serialized) override;
void OnConnectionError() override;
void OnFrameHeader(Http2StreamId stream_id,
size_t length,
diff --git a/http2/adapter/test_utils.h b/http2/adapter/test_utils.h
index ef1ae29..dccdfd9 100644
--- a/http2/adapter/test_utils.h
+++ b/http2/adapter/test_utils.h
@@ -17,13 +17,20 @@
class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> {
public:
- void Save(absl::string_view data) { absl::StrAppend(&data_, data); }
+ ssize_t OnReadyToSend(absl::string_view data) override {
+ const size_t to_accept = std::min(send_limit_, data.size());
+ absl::StrAppend(&data_, data.substr(0, to_accept));
+ return to_accept;
+ }
const std::string& data() { return data_; }
void Clear() { data_.clear(); }
+ void set_send_limit(size_t limit) { send_limit_ = limit; }
+
private:
std::string data_;
+ size_t send_limit_ = std::numeric_limits<size_t>::max();
};
// These matchers check whether a string consists entirely of HTTP/2 frames of