• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "net/spdy/spdy_http_stream.h"
6 
7 #include <algorithm>
8 #include <list>
9 #include <set>
10 #include <string>
11 #include <string_view>
12 #include <utility>
13 
14 #include "base/check_op.h"
15 #include "base/functional/bind.h"
16 #include "base/location.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/task/single_thread_task_runner.h"
19 #include "base/values.h"
20 #include "net/base/ip_endpoint.h"
21 #include "net/base/upload_data_stream.h"
22 #include "net/http/http_request_headers.h"
23 #include "net/http/http_request_info.h"
24 #include "net/http/http_response_info.h"
25 #include "net/log/net_log_event_type.h"
26 #include "net/log/net_log_with_source.h"
27 #include "net/socket/next_proto.h"
28 #include "net/spdy/spdy_http_utils.h"
29 #include "net/spdy/spdy_session.h"
30 #include "net/third_party/quiche/src/quiche/http2/core/spdy_protocol.h"
31 #include "url/scheme_host_port.h"
32 
33 namespace net {
34 
35 // Align our request body with |kMaxSpdyFrameChunkSize| to prevent unexpected
36 // buffer chunking. This is 16KB - frame header size.
37 const size_t SpdyHttpStream::kRequestBodyBufferSize = kMaxSpdyFrameChunkSize;
38 
SpdyHttpStream(const base::WeakPtr<SpdySession> & spdy_session,NetLogSource source_dependency,std::set<std::string> dns_aliases)39 SpdyHttpStream::SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session,
40                                NetLogSource source_dependency,
41                                std::set<std::string> dns_aliases)
42     : MultiplexedHttpStream(
43           std::make_unique<MultiplexedSessionHandle>(spdy_session)),
44       spdy_session_(spdy_session),
45       is_reused_(spdy_session_->IsReused()),
46       source_dependency_(source_dependency),
47       dns_aliases_(std::move(dns_aliases)) {
48   DCHECK(spdy_session_.get());
49 }
50 
~SpdyHttpStream()51 SpdyHttpStream::~SpdyHttpStream() {
52   if (stream_) {
53     stream_->DetachDelegate();
54     DCHECK(!stream_);
55   }
56 }
57 
RegisterRequest(const HttpRequestInfo * request_info)58 void SpdyHttpStream::RegisterRequest(const HttpRequestInfo* request_info) {
59   DCHECK(request_info);
60   request_info_ = request_info;
61 }
62 
InitializeStream(bool can_send_early,RequestPriority priority,const NetLogWithSource & stream_net_log,CompletionOnceCallback callback)63 int SpdyHttpStream::InitializeStream(bool can_send_early,
64                                      RequestPriority priority,
65                                      const NetLogWithSource& stream_net_log,
66                                      CompletionOnceCallback callback) {
67   DCHECK(!stream_);
68   DCHECK(request_info_);
69   if (!spdy_session_)
70     return ERR_CONNECTION_CLOSED;
71 
72   priority_ = priority;
73   int rv = stream_request_.StartRequest(
74       SPDY_REQUEST_RESPONSE_STREAM, spdy_session_, request_info_->url,
75       can_send_early, priority, request_info_->socket_tag, stream_net_log,
76       base::BindOnce(&SpdyHttpStream::OnStreamCreated,
77                      weak_factory_.GetWeakPtr(), std::move(callback)),
78       NetworkTrafficAnnotationTag{request_info_->traffic_annotation});
79 
80   if (rv == OK) {
81     stream_ = stream_request_.ReleaseStream().get();
82     InitializeStreamHelper();
83   }
84 
85   return rv;
86 }
87 
ReadResponseHeaders(CompletionOnceCallback callback)88 int SpdyHttpStream::ReadResponseHeaders(CompletionOnceCallback callback) {
89   CHECK(!callback.is_null());
90   if (stream_closed_)
91     return closed_stream_status_;
92 
93   CHECK(stream_);
94 
95   // Check if we already have the response headers. If so, return synchronously.
96   if (response_headers_complete_) {
97     CHECK(!stream_->IsIdle());
98     return OK;
99   }
100 
101   // Still waiting for the response, return IO_PENDING.
102   CHECK(response_callback_.is_null());
103   response_callback_ = std::move(callback);
104   return ERR_IO_PENDING;
105 }
106 
ReadResponseBody(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)107 int SpdyHttpStream::ReadResponseBody(IOBuffer* buf,
108                                      int buf_len,
109                                      CompletionOnceCallback callback) {
110   if (stream_)
111     CHECK(!stream_->IsIdle());
112 
113   CHECK(buf);
114   CHECK(buf_len);
115   CHECK(!callback.is_null());
116 
117   // If we have data buffered, complete the IO immediately.
118   if (!response_body_queue_.IsEmpty()) {
119     return response_body_queue_.Dequeue(buf->data(), buf_len);
120   } else if (stream_closed_) {
121     return closed_stream_status_;
122   }
123 
124   CHECK(response_callback_.is_null());
125   CHECK(!user_buffer_.get());
126   CHECK_EQ(0, user_buffer_len_);
127 
128   response_callback_ = std::move(callback);
129   user_buffer_ = buf;
130   user_buffer_len_ = buf_len;
131   return ERR_IO_PENDING;
132 }
133 
Close(bool not_reusable)134 void SpdyHttpStream::Close(bool not_reusable) {
135   // Note: the not_reusable flag has no meaning for SPDY streams.
136 
137   Cancel();
138   DCHECK(!stream_);
139 }
140 
IsResponseBodyComplete() const141 bool SpdyHttpStream::IsResponseBodyComplete() const {
142   return stream_closed_;
143 }
144 
IsConnectionReused() const145 bool SpdyHttpStream::IsConnectionReused() const {
146   return is_reused_;
147 }
148 
GetTotalReceivedBytes() const149 int64_t SpdyHttpStream::GetTotalReceivedBytes() const {
150   if (stream_closed_)
151     return closed_stream_received_bytes_;
152 
153   if (!stream_)
154     return 0;
155 
156   return stream_->raw_received_bytes();
157 }
158 
GetTotalSentBytes() const159 int64_t SpdyHttpStream::GetTotalSentBytes() const {
160   if (stream_closed_)
161     return closed_stream_sent_bytes_;
162 
163   if (!stream_)
164     return 0;
165 
166   return stream_->raw_sent_bytes();
167 }
168 
GetAlternativeService(AlternativeService * alternative_service) const169 bool SpdyHttpStream::GetAlternativeService(
170     AlternativeService* alternative_service) const {
171   return false;
172 }
173 
GetLoadTimingInfo(LoadTimingInfo * load_timing_info) const174 bool SpdyHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
175   if (stream_closed_) {
176     if (!closed_stream_has_load_timing_info_)
177       return false;
178     *load_timing_info = closed_stream_load_timing_info_;
179   } else {
180     // If |stream_| has yet to be created, or does not yet have an ID, fail.
181     // The reused flag can only be correctly set once a stream has an ID.
182     // Streams get their IDs once the request has been successfully sent, so
183     // this does not behave that differently from other stream types.
184     if (!stream_ || stream_->stream_id() == 0)
185       return false;
186 
187     if (!stream_->GetLoadTimingInfo(load_timing_info))
188       return false;
189   }
190 
191   // If the request waited for handshake confirmation, shift |ssl_end| to
192   // include that time.
193   if (!load_timing_info->connect_timing.ssl_end.is_null() &&
194       !stream_request_.confirm_handshake_end().is_null()) {
195     load_timing_info->connect_timing.ssl_end =
196         stream_request_.confirm_handshake_end();
197     load_timing_info->connect_timing.connect_end =
198         stream_request_.confirm_handshake_end();
199   }
200 
201   return true;
202 }
203 
SendRequest(const HttpRequestHeaders & request_headers,HttpResponseInfo * response,CompletionOnceCallback callback)204 int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
205                                 HttpResponseInfo* response,
206                                 CompletionOnceCallback callback) {
207   if (stream_closed_) {
208     return closed_stream_status_;
209   }
210 
211   base::Time request_time = base::Time::Now();
212   CHECK(stream_);
213 
214   stream_->SetRequestTime(request_time);
215   // This should only get called in the case of a request occurring
216   // during server push that has already begun but hasn't finished,
217   // so we set the response's request time to be the actual one
218   if (response_info_)
219     response_info_->request_time = request_time;
220 
221   CHECK(!request_body_buf_.get());
222   if (HasUploadData()) {
223     request_body_buf_ =
224         base::MakeRefCounted<IOBufferWithSize>(kRequestBodyBufferSize);
225     // The request body buffer is empty at first.
226     request_body_buf_size_ = 0;
227   }
228 
229   CHECK(!callback.is_null());
230   CHECK(response);
231   DCHECK(!response_info_);
232 
233   response_info_ = response;
234 
235   // Put the peer's IP address and port into the response.
236   IPEndPoint address;
237   int result = stream_->GetPeerAddress(&address);
238   if (result != OK)
239     return result;
240   response_info_->remote_endpoint = address;
241 
242   quiche::HttpHeaderBlock headers;
243   CreateSpdyHeadersFromHttpRequest(*request_info_, priority_, request_headers,
244                                    &headers);
245   DispatchRequestHeadersCallback(headers);
246 
247   bool will_send_data =
248       HasUploadData() || spdy_session_->EndStreamWithDataFrame();
249   result = stream_->SendRequestHeaders(
250       std::move(headers),
251       will_send_data ? MORE_DATA_TO_SEND : NO_MORE_DATA_TO_SEND);
252 
253   if (result == ERR_IO_PENDING) {
254     CHECK(request_callback_.is_null());
255     request_callback_ = std::move(callback);
256   }
257   return result;
258 }
259 
Cancel()260 void SpdyHttpStream::Cancel() {
261   request_callback_.Reset();
262   response_callback_.Reset();
263   if (stream_) {
264     stream_->Cancel(ERR_ABORTED);
265     DCHECK(!stream_);
266   }
267 }
268 
OnHeadersSent()269 void SpdyHttpStream::OnHeadersSent() {
270   if (HasUploadData()) {
271     ReadAndSendRequestBodyData();
272   } else if (spdy_session_->EndStreamWithDataFrame()) {
273     SendEmptyBody();
274   } else {
275     MaybePostRequestCallback(OK);
276   }
277 }
278 
OnEarlyHintsReceived(const quiche::HttpHeaderBlock & headers)279 void SpdyHttpStream::OnEarlyHintsReceived(
280     const quiche::HttpHeaderBlock& headers) {
281   DCHECK(!response_headers_complete_);
282   DCHECK(response_info_);
283   DCHECK_EQ(stream_->type(), SPDY_REQUEST_RESPONSE_STREAM);
284 
285   const int rv = SpdyHeadersToHttpResponse(headers, response_info_);
286   CHECK_NE(rv, ERR_INCOMPLETE_HTTP2_HEADERS);
287 
288   if (!response_callback_.is_null()) {
289     DoResponseCallback(OK);
290   }
291 }
292 
OnHeadersReceived(const quiche::HttpHeaderBlock & response_headers)293 void SpdyHttpStream::OnHeadersReceived(
294     const quiche::HttpHeaderBlock& response_headers) {
295   DCHECK(!response_headers_complete_);
296   DCHECK(response_info_);
297   response_headers_complete_ = true;
298 
299   const int rv = SpdyHeadersToHttpResponse(response_headers, response_info_);
300   DCHECK_NE(rv, ERR_INCOMPLETE_HTTP2_HEADERS);
301 
302   if (rv == ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION) {
303     // Cancel will call OnClose, which might call callbacks and might destroy
304     // `this`.
305     stream_->Cancel(rv);
306     return;
307   }
308 
309   response_info_->response_time = response_info_->original_response_time =
310       stream_->response_time();
311   // Don't store the SSLInfo in the response here, HttpNetworkTransaction
312   // will take care of that part.
313   CHECK_EQ(stream_->GetNegotiatedProtocol(), kProtoHTTP2);
314   response_info_->was_alpn_negotiated = true;
315   response_info_->request_time = stream_->GetRequestTime();
316   response_info_->connection_info = HttpConnectionInfo::kHTTP2;
317   response_info_->alpn_negotiated_protocol =
318       HttpConnectionInfoToString(response_info_->connection_info);
319 
320   // Invalidate HttpRequestInfo pointer. This is to allow |this| to be
321   // shared across multiple consumers at the cache layer which might require
322   // this stream to outlive the request_info_'s owner.
323   if (!upload_stream_in_progress_)
324     request_info_ = nullptr;
325 
326   if (!response_callback_.is_null()) {
327     DoResponseCallback(OK);
328   }
329 }
330 
OnDataReceived(std::unique_ptr<SpdyBuffer> buffer)331 void SpdyHttpStream::OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) {
332   DCHECK(response_headers_complete_);
333 
334   // Note that data may be received for a SpdyStream prior to the user calling
335   // ReadResponseBody(), therefore user_buffer_ may be NULL.  This may often
336   // happen for server initiated streams.
337   DCHECK(stream_);
338   DCHECK(!stream_->IsClosed());
339   if (buffer) {
340     response_body_queue_.Enqueue(std::move(buffer));
341     MaybeScheduleBufferedReadCallback();
342   }
343 }
344 
OnDataSent()345 void SpdyHttpStream::OnDataSent() {
346   if (request_info_ && HasUploadData()) {
347     request_body_buf_size_ = 0;
348     ReadAndSendRequestBodyData();
349   } else {
350     CHECK(spdy_session_->EndStreamWithDataFrame());
351     MaybePostRequestCallback(OK);
352   }
353 }
354 
355 // TODO(xunjieli): Maybe do something with the trailers. crbug.com/422958.
OnTrailers(const quiche::HttpHeaderBlock & trailers)356 void SpdyHttpStream::OnTrailers(const quiche::HttpHeaderBlock& trailers) {}
357 
OnClose(int status)358 void SpdyHttpStream::OnClose(int status) {
359   DCHECK(stream_);
360 
361   // Cancel any pending reads from the upload data stream.
362   if (request_info_ && request_info_->upload_data_stream)
363     request_info_->upload_data_stream->Reset();
364 
365   stream_closed_ = true;
366   closed_stream_status_ = status;
367   closed_stream_id_ = stream_->stream_id();
368   closed_stream_has_load_timing_info_ =
369       stream_->GetLoadTimingInfo(&closed_stream_load_timing_info_);
370   closed_stream_received_bytes_ = stream_->raw_received_bytes();
371   closed_stream_sent_bytes_ = stream_->raw_sent_bytes();
372   stream_ = nullptr;
373 
374   // Callbacks might destroy |this|.
375   base::WeakPtr<SpdyHttpStream> self = weak_factory_.GetWeakPtr();
376 
377   if (!request_callback_.is_null()) {
378     DoRequestCallback(status);
379     if (!self)
380       return;
381   }
382 
383   if (status == OK) {
384     // We need to complete any pending buffered read now.
385     DoBufferedReadCallback();
386     if (!self)
387       return;
388   }
389 
390   if (!response_callback_.is_null()) {
391     DoResponseCallback(status);
392   }
393 }
394 
CanGreaseFrameType() const395 bool SpdyHttpStream::CanGreaseFrameType() const {
396   return true;
397 }
398 
source_dependency() const399 NetLogSource SpdyHttpStream::source_dependency() const {
400   return source_dependency_;
401 }
402 
HasUploadData() const403 bool SpdyHttpStream::HasUploadData() const {
404   CHECK(request_info_);
405   return
406       request_info_->upload_data_stream &&
407       ((request_info_->upload_data_stream->size() > 0) ||
408        request_info_->upload_data_stream->is_chunked());
409 }
410 
OnStreamCreated(CompletionOnceCallback callback,int rv)411 void SpdyHttpStream::OnStreamCreated(CompletionOnceCallback callback, int rv) {
412   if (rv == OK) {
413     stream_ = stream_request_.ReleaseStream().get();
414     InitializeStreamHelper();
415   }
416   std::move(callback).Run(rv);
417 }
418 
ReadAndSendRequestBodyData()419 void SpdyHttpStream::ReadAndSendRequestBodyData() {
420   CHECK(HasUploadData());
421   upload_stream_in_progress_ = true;
422 
423   CHECK_EQ(request_body_buf_size_, 0);
424   if (request_info_->upload_data_stream->IsEOF()) {
425     MaybePostRequestCallback(OK);
426 
427     // Invalidate HttpRequestInfo pointer. This is to allow |this| to be
428     // shared across multiple consumers at the cache layer which might require
429     // this stream to outlive the request_info_'s owner.
430     upload_stream_in_progress_ = false;
431     if (response_headers_complete_)
432       request_info_ = nullptr;
433     return;
434   }
435 
436   // Read the data from the request body stream.
437   const int rv = request_info_->upload_data_stream->Read(
438       request_body_buf_.get(), request_body_buf_->size(),
439       base::BindOnce(&SpdyHttpStream::OnRequestBodyReadCompleted,
440                      weak_factory_.GetWeakPtr()));
441 
442   if (rv != ERR_IO_PENDING)
443     OnRequestBodyReadCompleted(rv);
444 }
445 
SendEmptyBody()446 void SpdyHttpStream::SendEmptyBody() {
447   CHECK(!HasUploadData());
448   CHECK(spdy_session_->EndStreamWithDataFrame());
449 
450   auto buffer = base::MakeRefCounted<IOBufferWithSize>(/* buffer_size = */ 0);
451   stream_->SendData(buffer.get(), /* length = */ 0, NO_MORE_DATA_TO_SEND);
452 }
453 
InitializeStreamHelper()454 void SpdyHttpStream::InitializeStreamHelper() {
455   stream_->SetDelegate(this);
456 }
457 
ResetStream(int error)458 void SpdyHttpStream::ResetStream(int error) {
459   spdy_session_->ResetStream(stream()->stream_id(), error, std::string());
460 }
461 
OnRequestBodyReadCompleted(int status)462 void SpdyHttpStream::OnRequestBodyReadCompleted(int status) {
463   if (status < 0) {
464     DCHECK_NE(ERR_IO_PENDING, status);
465     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
466         FROM_HERE, base::BindOnce(&SpdyHttpStream::ResetStream,
467                                   weak_factory_.GetWeakPtr(), status));
468 
469     return;
470   }
471 
472   CHECK_GE(status, 0);
473   request_body_buf_size_ = status;
474   const bool eof = request_info_->upload_data_stream->IsEOF();
475   // Only the final frame may have a length of 0.
476   if (eof) {
477     CHECK_GE(request_body_buf_size_, 0);
478   } else {
479     CHECK_GT(request_body_buf_size_, 0);
480   }
481   stream_->SendData(request_body_buf_.get(),
482                     request_body_buf_size_,
483                     eof ? NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND);
484 }
485 
MaybeScheduleBufferedReadCallback()486 void SpdyHttpStream::MaybeScheduleBufferedReadCallback() {
487   DCHECK(!stream_closed_);
488 
489   if (!user_buffer_.get())
490     return;
491 
492   // If enough data was received to fill the user buffer, invoke
493   // DoBufferedReadCallback() with no delay.
494   //
495   // Note: DoBufferedReadCallback() is invoked asynchronously to preserve
496   // historical behavior. It would be interesting to evaluate whether it can be
497   // invoked synchronously to avoid the overhead of posting a task. A long time
498   // ago, the callback was invoked synchronously
499   // https://codereview.chromium.org/652209/diff/2018/net/spdy/spdy_stream.cc.
500   if (response_body_queue_.GetTotalSize() >=
501       static_cast<size_t>(user_buffer_len_)) {
502     buffered_read_timer_.Start(FROM_HERE, base::TimeDelta() /* no delay */,
503                                this, &SpdyHttpStream::DoBufferedReadCallback);
504     return;
505   }
506 
507   // Handing small chunks of data to the caller creates measurable overhead.
508   // Wait 1ms to allow handing off multiple chunks of data received within a
509   // short time span at once.
510   buffered_read_timer_.Start(FROM_HERE, base::Milliseconds(1), this,
511                              &SpdyHttpStream::DoBufferedReadCallback);
512 }
513 
DoBufferedReadCallback()514 void SpdyHttpStream::DoBufferedReadCallback() {
515   buffered_read_timer_.Stop();
516 
517   // If the transaction is cancelled or errored out, we don't need to complete
518   // the read.
519   if (stream_closed_ && closed_stream_status_ != OK) {
520     if (response_callback_)
521       DoResponseCallback(closed_stream_status_);
522     return;
523   }
524 
525   if (!user_buffer_.get())
526     return;
527 
528   if (!response_body_queue_.IsEmpty()) {
529     int rv =
530         response_body_queue_.Dequeue(user_buffer_->data(), user_buffer_len_);
531     user_buffer_ = nullptr;
532     user_buffer_len_ = 0;
533     DoResponseCallback(rv);
534     return;
535   }
536 
537   if (stream_closed_ && response_callback_)
538     DoResponseCallback(closed_stream_status_);
539 }
540 
DoRequestCallback(int rv)541 void SpdyHttpStream::DoRequestCallback(int rv) {
542   CHECK_NE(rv, ERR_IO_PENDING);
543   CHECK(!request_callback_.is_null());
544   // Since Run may result in being called back, reset request_callback_ in
545   // advance.
546   std::move(request_callback_).Run(rv);
547 }
548 
MaybeDoRequestCallback(int rv)549 void SpdyHttpStream::MaybeDoRequestCallback(int rv) {
550   CHECK_NE(ERR_IO_PENDING, rv);
551   if (request_callback_)
552     std::move(request_callback_).Run(rv);
553 }
554 
MaybePostRequestCallback(int rv)555 void SpdyHttpStream::MaybePostRequestCallback(int rv) {
556   CHECK_NE(ERR_IO_PENDING, rv);
557   if (request_callback_)
558     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
559         FROM_HERE, base::BindOnce(&SpdyHttpStream::MaybeDoRequestCallback,
560                                   weak_factory_.GetWeakPtr(), rv));
561 }
562 
DoResponseCallback(int rv)563 void SpdyHttpStream::DoResponseCallback(int rv) {
564   CHECK_NE(rv, ERR_IO_PENDING);
565   CHECK(!response_callback_.is_null());
566 
567   // Since Run may result in being called back, reset response_callback_ in
568   // advance.
569   std::move(response_callback_).Run(rv);
570 }
571 
GetRemoteEndpoint(IPEndPoint * endpoint)572 int SpdyHttpStream::GetRemoteEndpoint(IPEndPoint* endpoint) {
573   if (!spdy_session_)
574     return ERR_SOCKET_NOT_CONNECTED;
575 
576   return spdy_session_->GetPeerAddress(endpoint);
577 }
578 
PopulateNetErrorDetails(NetErrorDetails * details)579 void SpdyHttpStream::PopulateNetErrorDetails(NetErrorDetails* details) {
580   details->connection_info = HttpConnectionInfo::kHTTP2;
581   return;
582 }
583 
SetPriority(RequestPriority priority)584 void SpdyHttpStream::SetPriority(RequestPriority priority) {
585   priority_ = priority;
586   if (stream_) {
587     stream_->SetPriority(priority);
588   }
589 }
590 
GetDnsAliases() const591 const std::set<std::string>& SpdyHttpStream::GetDnsAliases() const {
592   return dns_aliases_;
593 }
594 
GetAcceptChViaAlps() const595 std::string_view SpdyHttpStream::GetAcceptChViaAlps() const {
596   if (!request_info_) {
597     return {};
598   }
599 
600   return session()->GetAcceptChViaAlps(url::SchemeHostPort(request_info_->url));
601 }
602 
603 }  // namespace net
604