• 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 <utility>
12 
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/socket/next_proto.h"
27 #include "net/spdy/spdy_http_utils.h"
28 #include "net/spdy/spdy_session.h"
29 #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h"
30 #include "net/third_party/quiche/src/quiche/spdy/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   spdy::Http2HeaderBlock 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 spdy::Http2HeaderBlock & headers)279 void SpdyHttpStream::OnEarlyHintsReceived(
280     const spdy::Http2HeaderBlock& 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 spdy::Http2HeaderBlock & response_headers)293 void SpdyHttpStream::OnHeadersReceived(
294     const spdy::Http2HeaderBlock& 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 = stream_->response_time();
310   // Don't store the SSLInfo in the response here, HttpNetworkTransaction
311   // will take care of that part.
312   response_info_->was_alpn_negotiated = was_alpn_negotiated_;
313   response_info_->request_time = stream_->GetRequestTime();
314   response_info_->connection_info = HttpConnectionInfo::kHTTP2;
315   response_info_->alpn_negotiated_protocol =
316       HttpConnectionInfoToString(response_info_->connection_info);
317 
318   // Invalidate HttpRequestInfo pointer. This is to allow |this| to be
319   // shared across multiple consumers at the cache layer which might require
320   // this stream to outlive the request_info_'s owner.
321   if (!upload_stream_in_progress_)
322     request_info_ = nullptr;
323 
324   if (!response_callback_.is_null()) {
325     DoResponseCallback(OK);
326   }
327 }
328 
OnDataReceived(std::unique_ptr<SpdyBuffer> buffer)329 void SpdyHttpStream::OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) {
330   DCHECK(response_headers_complete_);
331 
332   // Note that data may be received for a SpdyStream prior to the user calling
333   // ReadResponseBody(), therefore user_buffer_ may be NULL.  This may often
334   // happen for server initiated streams.
335   DCHECK(stream_);
336   DCHECK(!stream_->IsClosed());
337   if (buffer) {
338     response_body_queue_.Enqueue(std::move(buffer));
339     MaybeScheduleBufferedReadCallback();
340   }
341 }
342 
OnDataSent()343 void SpdyHttpStream::OnDataSent() {
344   if (request_info_ && HasUploadData()) {
345     request_body_buf_size_ = 0;
346     ReadAndSendRequestBodyData();
347   } else {
348     CHECK(spdy_session_->EndStreamWithDataFrame());
349     MaybePostRequestCallback(OK);
350   }
351 }
352 
353 // TODO(xunjieli): Maybe do something with the trailers. crbug.com/422958.
OnTrailers(const spdy::Http2HeaderBlock & trailers)354 void SpdyHttpStream::OnTrailers(const spdy::Http2HeaderBlock& trailers) {}
355 
OnClose(int status)356 void SpdyHttpStream::OnClose(int status) {
357   DCHECK(stream_);
358 
359   // Cancel any pending reads from the upload data stream.
360   if (request_info_ && request_info_->upload_data_stream)
361     request_info_->upload_data_stream->Reset();
362 
363   stream_closed_ = true;
364   closed_stream_status_ = status;
365   closed_stream_id_ = stream_->stream_id();
366   closed_stream_has_load_timing_info_ =
367       stream_->GetLoadTimingInfo(&closed_stream_load_timing_info_);
368   closed_stream_received_bytes_ = stream_->raw_received_bytes();
369   closed_stream_sent_bytes_ = stream_->raw_sent_bytes();
370   stream_ = nullptr;
371 
372   // Callbacks might destroy |this|.
373   base::WeakPtr<SpdyHttpStream> self = weak_factory_.GetWeakPtr();
374 
375   if (!request_callback_.is_null()) {
376     DoRequestCallback(status);
377     if (!self)
378       return;
379   }
380 
381   if (status == OK) {
382     // We need to complete any pending buffered read now.
383     DoBufferedReadCallback();
384     if (!self)
385       return;
386   }
387 
388   if (!response_callback_.is_null()) {
389     DoResponseCallback(status);
390   }
391 }
392 
CanGreaseFrameType() const393 bool SpdyHttpStream::CanGreaseFrameType() const {
394   return true;
395 }
396 
source_dependency() const397 NetLogSource SpdyHttpStream::source_dependency() const {
398   return source_dependency_;
399 }
400 
HasUploadData() const401 bool SpdyHttpStream::HasUploadData() const {
402   CHECK(request_info_);
403   return
404       request_info_->upload_data_stream &&
405       ((request_info_->upload_data_stream->size() > 0) ||
406        request_info_->upload_data_stream->is_chunked());
407 }
408 
OnStreamCreated(CompletionOnceCallback callback,int rv)409 void SpdyHttpStream::OnStreamCreated(CompletionOnceCallback callback, int rv) {
410   if (rv == OK) {
411     stream_ = stream_request_.ReleaseStream().get();
412     InitializeStreamHelper();
413   }
414   std::move(callback).Run(rv);
415 }
416 
ReadAndSendRequestBodyData()417 void SpdyHttpStream::ReadAndSendRequestBodyData() {
418   CHECK(HasUploadData());
419   upload_stream_in_progress_ = true;
420 
421   CHECK_EQ(request_body_buf_size_, 0);
422   if (request_info_->upload_data_stream->IsEOF()) {
423     MaybePostRequestCallback(OK);
424 
425     // Invalidate HttpRequestInfo pointer. This is to allow |this| to be
426     // shared across multiple consumers at the cache layer which might require
427     // this stream to outlive the request_info_'s owner.
428     upload_stream_in_progress_ = false;
429     if (response_headers_complete_)
430       request_info_ = nullptr;
431     return;
432   }
433 
434   // Read the data from the request body stream.
435   const int rv = request_info_->upload_data_stream->Read(
436       request_body_buf_.get(), request_body_buf_->size(),
437       base::BindOnce(&SpdyHttpStream::OnRequestBodyReadCompleted,
438                      weak_factory_.GetWeakPtr()));
439 
440   if (rv != ERR_IO_PENDING)
441     OnRequestBodyReadCompleted(rv);
442 }
443 
SendEmptyBody()444 void SpdyHttpStream::SendEmptyBody() {
445   CHECK(!HasUploadData());
446   CHECK(spdy_session_->EndStreamWithDataFrame());
447 
448   auto buffer = base::MakeRefCounted<IOBufferWithSize>(/* buffer_size = */ 0);
449   stream_->SendData(buffer.get(), /* length = */ 0, NO_MORE_DATA_TO_SEND);
450 }
451 
InitializeStreamHelper()452 void SpdyHttpStream::InitializeStreamHelper() {
453   stream_->SetDelegate(this);
454   was_alpn_negotiated_ = stream_->GetNegotiatedProtocol() != kProtoUnknown;
455 }
456 
ResetStream(int error)457 void SpdyHttpStream::ResetStream(int error) {
458   spdy_session_->ResetStream(stream()->stream_id(), error, std::string());
459 }
460 
OnRequestBodyReadCompleted(int status)461 void SpdyHttpStream::OnRequestBodyReadCompleted(int status) {
462   if (status < 0) {
463     DCHECK_NE(ERR_IO_PENDING, status);
464     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
465         FROM_HERE, base::BindOnce(&SpdyHttpStream::ResetStream,
466                                   weak_factory_.GetWeakPtr(), status));
467 
468     return;
469   }
470 
471   CHECK_GE(status, 0);
472   request_body_buf_size_ = status;
473   const bool eof = request_info_->upload_data_stream->IsEOF();
474   // Only the final frame may have a length of 0.
475   if (eof) {
476     CHECK_GE(request_body_buf_size_, 0);
477   } else {
478     CHECK_GT(request_body_buf_size_, 0);
479   }
480   stream_->SendData(request_body_buf_.get(),
481                     request_body_buf_size_,
482                     eof ? NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND);
483 }
484 
MaybeScheduleBufferedReadCallback()485 void SpdyHttpStream::MaybeScheduleBufferedReadCallback() {
486   DCHECK(!stream_closed_);
487 
488   if (!user_buffer_.get())
489     return;
490 
491   // If enough data was received to fill the user buffer, invoke
492   // DoBufferedReadCallback() with no delay.
493   //
494   // Note: DoBufferedReadCallback() is invoked asynchronously to preserve
495   // historical behavior. It would be interesting to evaluate whether it can be
496   // invoked synchronously to avoid the overhead of posting a task. A long time
497   // ago, the callback was invoked synchronously
498   // https://codereview.chromium.org/652209/diff/2018/net/spdy/spdy_stream.cc.
499   if (response_body_queue_.GetTotalSize() >=
500       static_cast<size_t>(user_buffer_len_)) {
501     buffered_read_timer_.Start(FROM_HERE, base::TimeDelta() /* no delay */,
502                                this, &SpdyHttpStream::DoBufferedReadCallback);
503     return;
504   }
505 
506   // Handing small chunks of data to the caller creates measurable overhead.
507   // Wait 1ms to allow handing off multiple chunks of data received within a
508   // short time span at once.
509   buffered_read_timer_.Start(FROM_HERE, base::Milliseconds(1), this,
510                              &SpdyHttpStream::DoBufferedReadCallback);
511 }
512 
DoBufferedReadCallback()513 void SpdyHttpStream::DoBufferedReadCallback() {
514   buffered_read_timer_.Stop();
515 
516   // If the transaction is cancelled or errored out, we don't need to complete
517   // the read.
518   if (stream_closed_ && closed_stream_status_ != OK) {
519     if (response_callback_)
520       DoResponseCallback(closed_stream_status_);
521     return;
522   }
523 
524   if (!user_buffer_.get())
525     return;
526 
527   if (!response_body_queue_.IsEmpty()) {
528     int rv =
529         response_body_queue_.Dequeue(user_buffer_->data(), user_buffer_len_);
530     user_buffer_ = nullptr;
531     user_buffer_len_ = 0;
532     DoResponseCallback(rv);
533     return;
534   }
535 
536   if (stream_closed_ && response_callback_)
537     DoResponseCallback(closed_stream_status_);
538 }
539 
DoRequestCallback(int rv)540 void SpdyHttpStream::DoRequestCallback(int rv) {
541   CHECK_NE(rv, ERR_IO_PENDING);
542   CHECK(!request_callback_.is_null());
543   // Since Run may result in being called back, reset request_callback_ in
544   // advance.
545   std::move(request_callback_).Run(rv);
546 }
547 
MaybeDoRequestCallback(int rv)548 void SpdyHttpStream::MaybeDoRequestCallback(int rv) {
549   CHECK_NE(ERR_IO_PENDING, rv);
550   if (request_callback_)
551     std::move(request_callback_).Run(rv);
552 }
553 
MaybePostRequestCallback(int rv)554 void SpdyHttpStream::MaybePostRequestCallback(int rv) {
555   CHECK_NE(ERR_IO_PENDING, rv);
556   if (request_callback_)
557     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
558         FROM_HERE, base::BindOnce(&SpdyHttpStream::MaybeDoRequestCallback,
559                                   weak_factory_.GetWeakPtr(), rv));
560 }
561 
DoResponseCallback(int rv)562 void SpdyHttpStream::DoResponseCallback(int rv) {
563   CHECK_NE(rv, ERR_IO_PENDING);
564   CHECK(!response_callback_.is_null());
565 
566   // Since Run may result in being called back, reset response_callback_ in
567   // advance.
568   std::move(response_callback_).Run(rv);
569 }
570 
GetRemoteEndpoint(IPEndPoint * endpoint)571 int SpdyHttpStream::GetRemoteEndpoint(IPEndPoint* endpoint) {
572   if (!spdy_session_)
573     return ERR_SOCKET_NOT_CONNECTED;
574 
575   return spdy_session_->GetPeerAddress(endpoint);
576 }
577 
PopulateNetErrorDetails(NetErrorDetails * details)578 void SpdyHttpStream::PopulateNetErrorDetails(NetErrorDetails* details) {
579   details->connection_info = HttpConnectionInfo::kHTTP2;
580   return;
581 }
582 
SetPriority(RequestPriority priority)583 void SpdyHttpStream::SetPriority(RequestPriority priority) {
584   priority_ = priority;
585   if (stream_) {
586     stream_->SetPriority(priority);
587   }
588 }
589 
GetDnsAliases() const590 const std::set<std::string>& SpdyHttpStream::GetDnsAliases() const {
591   return dns_aliases_;
592 }
593 
GetAcceptChViaAlps() const594 base::StringPiece SpdyHttpStream::GetAcceptChViaAlps() const {
595   if (!request_info_) {
596     return {};
597   }
598 
599   return session()->GetAcceptChViaAlps(url::SchemeHostPort(request_info_->url));
600 }
601 
602 }  // namespace net
603