1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/spdy/spdy_http_stream.h"
13 #include "base/check_op.h"
14 #include "base/functional/bind.h"
15 #include "base/location.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/task/single_thread_task_runner.h"
18 #include "base/values.h"
19 #include "net/base/ip_endpoint.h"
20 #include "net/base/upload_data_stream.h"
21 #include "net/http/http_request_headers.h"
22 #include "net/http/http_request_info.h"
23 #include "net/http/http_response_info.h"
24 #include "net/log/net_log_event_type.h"
25 #include "net/log/net_log_with_source.h"
26 #include "net/spdy/spdy_http_utils.h"
27 #include "net/spdy/spdy_session.h"
28 #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h"
29 #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
30 #include "url/scheme_host_port.h"
32 #if BUILDFLAG(IS_TIZEN_TV)
33 #include "net/http/http_response_headers.h"
34 #include "tizen_src/chromium_impl/base/logging_network.h"
39 // Align our request body with |kMaxSpdyFrameChunkSize| to prevent unexpected
40 // buffer chunking. This is 16KB - frame header size.
41 const size_t SpdyHttpStream::kRequestBodyBufferSize = kMaxSpdyFrameChunkSize;
43 SpdyHttpStream::SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session,
44 NetLogSource source_dependency,
45 std::set<std::string> dns_aliases)
46 : MultiplexedHttpStream(
47 std::make_unique<MultiplexedSessionHandle>(spdy_session)),
48 spdy_session_(spdy_session),
49 is_reused_(spdy_session_->IsReused()),
50 source_dependency_(source_dependency),
51 dns_aliases_(std::move(dns_aliases)) {
52 DCHECK(spdy_session_.get());
55 SpdyHttpStream::~SpdyHttpStream() {
57 stream_->DetachDelegate();
62 void SpdyHttpStream::RegisterRequest(const HttpRequestInfo* request_info) {
64 request_info_ = request_info;
67 int SpdyHttpStream::InitializeStream(bool can_send_early,
68 RequestPriority priority,
69 const NetLogWithSource& stream_net_log,
70 CompletionOnceCallback callback) {
72 DCHECK(request_info_);
74 return ERR_CONNECTION_CLOSED;
77 int rv = stream_request_.StartRequest(
78 SPDY_REQUEST_RESPONSE_STREAM, spdy_session_, request_info_->url,
79 can_send_early, priority, request_info_->socket_tag, stream_net_log,
80 base::BindOnce(&SpdyHttpStream::OnStreamCreated,
81 weak_factory_.GetWeakPtr(), std::move(callback)),
82 NetworkTrafficAnnotationTag{request_info_->traffic_annotation});
85 stream_ = stream_request_.ReleaseStream().get();
86 InitializeStreamHelper();
92 int SpdyHttpStream::ReadResponseHeaders(CompletionOnceCallback callback) {
93 CHECK(!callback.is_null());
95 return closed_stream_status_;
99 // Check if we already have the response headers. If so, return synchronously.
100 if (response_headers_complete_) {
101 CHECK(!stream_->IsIdle());
105 // Still waiting for the response, return IO_PENDING.
106 CHECK(response_callback_.is_null());
107 response_callback_ = std::move(callback);
108 return ERR_IO_PENDING;
111 int SpdyHttpStream::ReadResponseBody(IOBuffer* buf,
113 CompletionOnceCallback callback) {
115 CHECK(!stream_->IsIdle());
119 CHECK(!callback.is_null());
121 // If we have data buffered, complete the IO immediately.
122 if (!response_body_queue_.IsEmpty()) {
123 return response_body_queue_.Dequeue(buf->data(), buf_len);
124 } else if (stream_closed_) {
125 return closed_stream_status_;
128 CHECK(response_callback_.is_null());
129 CHECK(!user_buffer_.get());
130 CHECK_EQ(0, user_buffer_len_);
132 response_callback_ = std::move(callback);
134 user_buffer_len_ = buf_len;
135 return ERR_IO_PENDING;
138 void SpdyHttpStream::Close(bool not_reusable) {
139 // Note: the not_reusable flag has no meaning for SPDY streams.
145 bool SpdyHttpStream::IsResponseBodyComplete() const {
146 return stream_closed_;
149 bool SpdyHttpStream::IsConnectionReused() const {
153 int64_t SpdyHttpStream::GetTotalReceivedBytes() const {
155 return closed_stream_received_bytes_;
160 return stream_->raw_received_bytes();
163 int64_t SpdyHttpStream::GetTotalSentBytes() const {
165 return closed_stream_sent_bytes_;
170 return stream_->raw_sent_bytes();
173 bool SpdyHttpStream::GetAlternativeService(
174 AlternativeService* alternative_service) const {
178 bool SpdyHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
179 if (stream_closed_) {
180 if (!closed_stream_has_load_timing_info_)
182 *load_timing_info = closed_stream_load_timing_info_;
184 // If |stream_| has yet to be created, or does not yet have an ID, fail.
185 // The reused flag can only be correctly set once a stream has an ID.
186 // Streams get their IDs once the request has been successfully sent, so
187 // this does not behave that differently from other stream types.
188 if (!stream_ || stream_->stream_id() == 0)
191 if (!stream_->GetLoadTimingInfo(load_timing_info))
195 // If the request waited for handshake confirmation, shift |ssl_end| to
196 // include that time.
197 if (!load_timing_info->connect_timing.ssl_end.is_null() &&
198 !stream_request_.confirm_handshake_end().is_null()) {
199 load_timing_info->connect_timing.ssl_end =
200 stream_request_.confirm_handshake_end();
201 load_timing_info->connect_timing.connect_end =
202 stream_request_.confirm_handshake_end();
208 int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
209 HttpResponseInfo* response,
210 CompletionOnceCallback callback) {
211 if (stream_closed_) {
212 return closed_stream_status_;
215 base::Time request_time = base::Time::Now();
218 stream_->SetRequestTime(request_time);
219 // This should only get called in the case of a request occurring
220 // during server push that has already begun but hasn't finished,
221 // so we set the response's request time to be the actual one
223 response_info_->request_time = request_time;
225 CHECK(!request_body_buf_.get());
226 if (HasUploadData()) {
228 base::MakeRefCounted<IOBufferWithSize>(kRequestBodyBufferSize);
229 // The request body buffer is empty at first.
230 request_body_buf_size_ = 0;
233 CHECK(!callback.is_null());
235 DCHECK(!response_info_);
237 response_info_ = response;
239 // Put the peer's IP address and port into the response.
241 int result = stream_->GetPeerAddress(&address);
244 response_info_->remote_endpoint = address;
246 #if BUILDFLAG(IS_TIZEN_TV)
247 NET_LOGD("Request URL: %s", request_info_->url.spec().c_str());
248 NET_LOGD("Request Header: %s", request_headers.ToString().c_str());
251 spdy::Http2HeaderBlock headers;
252 CreateSpdyHeadersFromHttpRequest(*request_info_, priority_, request_headers,
254 DispatchRequestHeadersCallback(headers);
256 bool will_send_data =
257 HasUploadData() || spdy_session_->EndStreamWithDataFrame();
258 result = stream_->SendRequestHeaders(
260 will_send_data ? MORE_DATA_TO_SEND : NO_MORE_DATA_TO_SEND);
262 if (result == ERR_IO_PENDING) {
263 CHECK(request_callback_.is_null());
264 request_callback_ = std::move(callback);
269 void SpdyHttpStream::Cancel() {
270 request_callback_.Reset();
271 response_callback_.Reset();
273 stream_->Cancel(ERR_ABORTED);
278 void SpdyHttpStream::OnHeadersSent() {
279 if (HasUploadData()) {
280 ReadAndSendRequestBodyData();
281 } else if (spdy_session_->EndStreamWithDataFrame()) {
284 MaybePostRequestCallback(OK);
288 void SpdyHttpStream::OnEarlyHintsReceived(
289 const spdy::Http2HeaderBlock& headers) {
290 DCHECK(!response_headers_complete_);
291 DCHECK(response_info_);
292 DCHECK_EQ(stream_->type(), SPDY_REQUEST_RESPONSE_STREAM);
294 const int rv = SpdyHeadersToHttpResponse(headers, response_info_);
295 CHECK_NE(rv, ERR_INCOMPLETE_HTTP2_HEADERS);
297 if (!response_callback_.is_null()) {
298 DoResponseCallback(OK);
302 void SpdyHttpStream::OnHeadersReceived(
303 const spdy::Http2HeaderBlock& response_headers) {
304 DCHECK(!response_headers_complete_);
305 DCHECK(response_info_);
306 response_headers_complete_ = true;
308 const int rv = SpdyHeadersToHttpResponse(response_headers, response_info_);
309 DCHECK_NE(rv, ERR_INCOMPLETE_HTTP2_HEADERS);
311 if (rv == ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION) {
312 // Cancel will call OnClose, which might call callbacks and might destroy
318 #if BUILDFLAG(IS_TIZEN_TV)
320 NET_LOGD("Response URL: %s", request_info_->url.spec().c_str());
322 NET_LOGD("Response URL: Error, no request info");
324 HttpResponseHeaders* headers = response_info_->headers.get();
325 std::string response_header = headers->GetStatusLine();
326 response_header.push_back('\n');
328 std::string name, value;
329 while (headers->EnumerateHeaderLines(&iter, &name, &value))
330 response_header.append(name).append(": ").append(value).push_back('\n');
331 NET_LOGD("Response Header: %s\n", response_header.c_str());
334 response_info_->response_time = stream_->response_time();
335 // Don't store the SSLInfo in the response here, HttpNetworkTransaction
336 // will take care of that part.
337 response_info_->was_alpn_negotiated = was_alpn_negotiated_;
338 response_info_->request_time = stream_->GetRequestTime();
339 response_info_->connection_info = HttpResponseInfo::CONNECTION_INFO_HTTP2;
340 response_info_->alpn_negotiated_protocol =
341 HttpResponseInfo::ConnectionInfoToString(response_info_->connection_info);
343 // Invalidate HttpRequestInfo pointer. This is to allow |this| to be
344 // shared across multiple consumers at the cache layer which might require
345 // this stream to outlive the request_info_'s owner.
346 if (!upload_stream_in_progress_)
347 request_info_ = nullptr;
349 if (!response_callback_.is_null()) {
350 DoResponseCallback(OK);
354 void SpdyHttpStream::OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) {
355 DCHECK(response_headers_complete_);
357 // Note that data may be received for a SpdyStream prior to the user calling
358 // ReadResponseBody(), therefore user_buffer_ may be NULL. This may often
359 // happen for server initiated streams.
361 DCHECK(!stream_->IsClosed());
363 response_body_queue_.Enqueue(std::move(buffer));
364 MaybeScheduleBufferedReadCallback();
368 void SpdyHttpStream::OnDataSent() {
369 if (request_info_ && HasUploadData()) {
370 request_body_buf_size_ = 0;
371 ReadAndSendRequestBodyData();
373 CHECK(spdy_session_->EndStreamWithDataFrame());
374 MaybePostRequestCallback(OK);
378 // TODO(xunjieli): Maybe do something with the trailers. crbug.com/422958.
379 void SpdyHttpStream::OnTrailers(const spdy::Http2HeaderBlock& trailers) {}
381 void SpdyHttpStream::OnClose(int status) {
384 // Cancel any pending reads from the upload data stream.
385 if (request_info_ && request_info_->upload_data_stream)
386 request_info_->upload_data_stream->Reset();
388 stream_closed_ = true;
389 closed_stream_status_ = status;
390 closed_stream_id_ = stream_->stream_id();
391 closed_stream_has_load_timing_info_ =
392 stream_->GetLoadTimingInfo(&closed_stream_load_timing_info_);
393 closed_stream_received_bytes_ = stream_->raw_received_bytes();
394 closed_stream_sent_bytes_ = stream_->raw_sent_bytes();
397 // Callbacks might destroy |this|.
398 base::WeakPtr<SpdyHttpStream> self = weak_factory_.GetWeakPtr();
400 if (!request_callback_.is_null()) {
401 DoRequestCallback(status);
407 // We need to complete any pending buffered read now.
408 DoBufferedReadCallback();
413 if (!response_callback_.is_null()) {
414 DoResponseCallback(status);
418 bool SpdyHttpStream::CanGreaseFrameType() const {
422 NetLogSource SpdyHttpStream::source_dependency() const {
423 return source_dependency_;
426 bool SpdyHttpStream::HasUploadData() const {
427 CHECK(request_info_);
429 request_info_->upload_data_stream &&
430 ((request_info_->upload_data_stream->size() > 0) ||
431 request_info_->upload_data_stream->is_chunked());
434 void SpdyHttpStream::OnStreamCreated(CompletionOnceCallback callback, int rv) {
436 stream_ = stream_request_.ReleaseStream().get();
437 InitializeStreamHelper();
439 std::move(callback).Run(rv);
442 void SpdyHttpStream::ReadAndSendRequestBodyData() {
443 CHECK(HasUploadData());
444 upload_stream_in_progress_ = true;
446 CHECK_EQ(request_body_buf_size_, 0);
447 if (request_info_->upload_data_stream->IsEOF()) {
448 MaybePostRequestCallback(OK);
450 // Invalidate HttpRequestInfo pointer. This is to allow |this| to be
451 // shared across multiple consumers at the cache layer which might require
452 // this stream to outlive the request_info_'s owner.
453 upload_stream_in_progress_ = false;
454 if (response_headers_complete_)
455 request_info_ = nullptr;
459 // Read the data from the request body stream.
460 const int rv = request_info_->upload_data_stream->Read(
461 request_body_buf_.get(), request_body_buf_->size(),
462 base::BindOnce(&SpdyHttpStream::OnRequestBodyReadCompleted,
463 weak_factory_.GetWeakPtr()));
465 if (rv != ERR_IO_PENDING)
466 OnRequestBodyReadCompleted(rv);
469 void SpdyHttpStream::SendEmptyBody() {
470 CHECK(!HasUploadData());
471 CHECK(spdy_session_->EndStreamWithDataFrame());
473 auto buffer = base::MakeRefCounted<IOBuffer>(/* buffer_size = */ 0);
474 stream_->SendData(buffer.get(), /* length = */ 0, NO_MORE_DATA_TO_SEND);
477 void SpdyHttpStream::InitializeStreamHelper() {
478 stream_->SetDelegate(this);
479 was_alpn_negotiated_ = stream_->WasAlpnNegotiated();
482 void SpdyHttpStream::ResetStream(int error) {
483 spdy_session_->ResetStream(stream()->stream_id(), error, std::string());
486 void SpdyHttpStream::OnRequestBodyReadCompleted(int status) {
488 DCHECK_NE(ERR_IO_PENDING, status);
489 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
490 FROM_HERE, base::BindOnce(&SpdyHttpStream::ResetStream,
491 weak_factory_.GetWeakPtr(), status));
497 request_body_buf_size_ = status;
498 const bool eof = request_info_->upload_data_stream->IsEOF();
499 // Only the final frame may have a length of 0.
501 CHECK_GE(request_body_buf_size_, 0);
503 CHECK_GT(request_body_buf_size_, 0);
505 stream_->SendData(request_body_buf_.get(),
506 request_body_buf_size_,
507 eof ? NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND);
510 void SpdyHttpStream::MaybeScheduleBufferedReadCallback() {
511 DCHECK(!stream_closed_);
513 if (!user_buffer_.get())
516 // If enough data was received to fill the user buffer, invoke
517 // DoBufferedReadCallback() with no delay.
519 // Note: DoBufferedReadCallback() is invoked asynchronously to preserve
520 // historical behavior. It would be interesting to evaluate whether it can be
521 // invoked synchronously to avoid the overhead of posting a task. A long time
522 // ago, the callback was invoked synchronously
523 // https://codereview.chromium.org/652209/diff/2018/net/spdy/spdy_stream.cc.
524 if (response_body_queue_.GetTotalSize() >=
525 static_cast<size_t>(user_buffer_len_)) {
526 buffered_read_timer_.Start(FROM_HERE, base::TimeDelta() /* no delay */,
527 this, &SpdyHttpStream::DoBufferedReadCallback);
531 // Handing small chunks of data to the caller creates measurable overhead.
532 // Wait 1ms to allow handing off multiple chunks of data received within a
533 // short time span at once.
534 buffered_read_timer_.Start(FROM_HERE, base::Milliseconds(1), this,
535 &SpdyHttpStream::DoBufferedReadCallback);
538 void SpdyHttpStream::DoBufferedReadCallback() {
539 buffered_read_timer_.Stop();
541 // If the transaction is cancelled or errored out, we don't need to complete
543 if (stream_closed_ && closed_stream_status_ != OK) {
544 if (response_callback_)
545 DoResponseCallback(closed_stream_status_);
549 if (!user_buffer_.get())
552 if (!response_body_queue_.IsEmpty()) {
554 response_body_queue_.Dequeue(user_buffer_->data(), user_buffer_len_);
555 user_buffer_ = nullptr;
556 user_buffer_len_ = 0;
557 DoResponseCallback(rv);
561 if (stream_closed_ && response_callback_)
562 DoResponseCallback(closed_stream_status_);
565 void SpdyHttpStream::DoRequestCallback(int rv) {
566 CHECK_NE(rv, ERR_IO_PENDING);
567 CHECK(!request_callback_.is_null());
568 // Since Run may result in being called back, reset request_callback_ in
570 std::move(request_callback_).Run(rv);
573 void SpdyHttpStream::MaybeDoRequestCallback(int rv) {
574 CHECK_NE(ERR_IO_PENDING, rv);
575 if (request_callback_)
576 std::move(request_callback_).Run(rv);
579 void SpdyHttpStream::MaybePostRequestCallback(int rv) {
580 CHECK_NE(ERR_IO_PENDING, rv);
581 if (request_callback_)
582 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
583 FROM_HERE, base::BindOnce(&SpdyHttpStream::MaybeDoRequestCallback,
584 weak_factory_.GetWeakPtr(), rv));
587 void SpdyHttpStream::DoResponseCallback(int rv) {
588 CHECK_NE(rv, ERR_IO_PENDING);
589 CHECK(!response_callback_.is_null());
591 // Since Run may result in being called back, reset response_callback_ in
593 std::move(response_callback_).Run(rv);
596 int SpdyHttpStream::GetRemoteEndpoint(IPEndPoint* endpoint) {
598 return ERR_SOCKET_NOT_CONNECTED;
600 return spdy_session_->GetPeerAddress(endpoint);
603 void SpdyHttpStream::PopulateNetErrorDetails(NetErrorDetails* details) {
604 details->connection_info = HttpResponseInfo::CONNECTION_INFO_HTTP2;
608 void SpdyHttpStream::SetPriority(RequestPriority priority) {
609 priority_ = priority;
611 stream_->SetPriority(priority);
615 const std::set<std::string>& SpdyHttpStream::GetDnsAliases() const {
619 base::StringPiece SpdyHttpStream::GetAcceptChViaAlps() const {
620 if (!request_info_) {
624 return session()->GetAcceptChViaAlps(url::SchemeHostPort(request_info_->url));