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