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/spdy/core/http2_header_block.h"
31 #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
32 #include "url/scheme_host_port.h"
33
34 namespace net {
35
36 // Align our request body with |kMaxSpdyFrameChunkSize| to prevent unexpected
37 // buffer chunking. This is 16KB - frame header size.
38 const size_t SpdyHttpStream::kRequestBodyBufferSize = kMaxSpdyFrameChunkSize;
39
SpdyHttpStream(const base::WeakPtr<SpdySession> & spdy_session,NetLogSource source_dependency,std::set<std::string> dns_aliases)40 SpdyHttpStream::SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session,
41 NetLogSource source_dependency,
42 std::set<std::string> dns_aliases)
43 : MultiplexedHttpStream(
44 std::make_unique<MultiplexedSessionHandle>(spdy_session)),
45 spdy_session_(spdy_session),
46 is_reused_(spdy_session_->IsReused()),
47 source_dependency_(source_dependency),
48 dns_aliases_(std::move(dns_aliases)) {
49 DCHECK(spdy_session_.get());
50 }
51
~SpdyHttpStream()52 SpdyHttpStream::~SpdyHttpStream() {
53 if (stream_) {
54 stream_->DetachDelegate();
55 DCHECK(!stream_);
56 }
57 }
58
RegisterRequest(const HttpRequestInfo * request_info)59 void SpdyHttpStream::RegisterRequest(const HttpRequestInfo* request_info) {
60 DCHECK(request_info);
61 request_info_ = request_info;
62 }
63
InitializeStream(bool can_send_early,RequestPriority priority,const NetLogWithSource & stream_net_log,CompletionOnceCallback callback)64 int SpdyHttpStream::InitializeStream(bool can_send_early,
65 RequestPriority priority,
66 const NetLogWithSource& stream_net_log,
67 CompletionOnceCallback callback) {
68 DCHECK(!stream_);
69 DCHECK(request_info_);
70 if (!spdy_session_)
71 return ERR_CONNECTION_CLOSED;
72
73 priority_ = priority;
74 int rv = stream_request_.StartRequest(
75 SPDY_REQUEST_RESPONSE_STREAM, spdy_session_, request_info_->url,
76 can_send_early, priority, request_info_->socket_tag, stream_net_log,
77 base::BindOnce(&SpdyHttpStream::OnStreamCreated,
78 weak_factory_.GetWeakPtr(), std::move(callback)),
79 NetworkTrafficAnnotationTag{request_info_->traffic_annotation});
80
81 if (rv == OK) {
82 stream_ = stream_request_.ReleaseStream().get();
83 InitializeStreamHelper();
84 }
85
86 return rv;
87 }
88
ReadResponseHeaders(CompletionOnceCallback callback)89 int SpdyHttpStream::ReadResponseHeaders(CompletionOnceCallback callback) {
90 CHECK(!callback.is_null());
91 if (stream_closed_)
92 return closed_stream_status_;
93
94 CHECK(stream_);
95
96 // Check if we already have the response headers. If so, return synchronously.
97 if (response_headers_complete_) {
98 CHECK(!stream_->IsIdle());
99 return OK;
100 }
101
102 // Still waiting for the response, return IO_PENDING.
103 CHECK(response_callback_.is_null());
104 response_callback_ = std::move(callback);
105 return ERR_IO_PENDING;
106 }
107
ReadResponseBody(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)108 int SpdyHttpStream::ReadResponseBody(IOBuffer* buf,
109 int buf_len,
110 CompletionOnceCallback callback) {
111 if (stream_)
112 CHECK(!stream_->IsIdle());
113
114 CHECK(buf);
115 CHECK(buf_len);
116 CHECK(!callback.is_null());
117
118 // If we have data buffered, complete the IO immediately.
119 if (!response_body_queue_.IsEmpty()) {
120 return response_body_queue_.Dequeue(buf->data(), buf_len);
121 } else if (stream_closed_) {
122 return closed_stream_status_;
123 }
124
125 CHECK(response_callback_.is_null());
126 CHECK(!user_buffer_.get());
127 CHECK_EQ(0, user_buffer_len_);
128
129 response_callback_ = std::move(callback);
130 user_buffer_ = buf;
131 user_buffer_len_ = buf_len;
132 return ERR_IO_PENDING;
133 }
134
Close(bool not_reusable)135 void SpdyHttpStream::Close(bool not_reusable) {
136 // Note: the not_reusable flag has no meaning for SPDY streams.
137
138 Cancel();
139 DCHECK(!stream_);
140 }
141
IsResponseBodyComplete() const142 bool SpdyHttpStream::IsResponseBodyComplete() const {
143 return stream_closed_;
144 }
145
IsConnectionReused() const146 bool SpdyHttpStream::IsConnectionReused() const {
147 return is_reused_;
148 }
149
GetTotalReceivedBytes() const150 int64_t SpdyHttpStream::GetTotalReceivedBytes() const {
151 if (stream_closed_)
152 return closed_stream_received_bytes_;
153
154 if (!stream_)
155 return 0;
156
157 return stream_->raw_received_bytes();
158 }
159
GetTotalSentBytes() const160 int64_t SpdyHttpStream::GetTotalSentBytes() const {
161 if (stream_closed_)
162 return closed_stream_sent_bytes_;
163
164 if (!stream_)
165 return 0;
166
167 return stream_->raw_sent_bytes();
168 }
169
GetAlternativeService(AlternativeService * alternative_service) const170 bool SpdyHttpStream::GetAlternativeService(
171 AlternativeService* alternative_service) const {
172 return false;
173 }
174
GetLoadTimingInfo(LoadTimingInfo * load_timing_info) const175 bool SpdyHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
176 if (stream_closed_) {
177 if (!closed_stream_has_load_timing_info_)
178 return false;
179 *load_timing_info = closed_stream_load_timing_info_;
180 } else {
181 // If |stream_| has yet to be created, or does not yet have an ID, fail.
182 // The reused flag can only be correctly set once a stream has an ID.
183 // Streams get their IDs once the request has been successfully sent, so
184 // this does not behave that differently from other stream types.
185 if (!stream_ || stream_->stream_id() == 0)
186 return false;
187
188 if (!stream_->GetLoadTimingInfo(load_timing_info))
189 return false;
190 }
191
192 // If the request waited for handshake confirmation, shift |ssl_end| to
193 // include that time.
194 if (!load_timing_info->connect_timing.ssl_end.is_null() &&
195 !stream_request_.confirm_handshake_end().is_null()) {
196 load_timing_info->connect_timing.ssl_end =
197 stream_request_.confirm_handshake_end();
198 load_timing_info->connect_timing.connect_end =
199 stream_request_.confirm_handshake_end();
200 }
201
202 return true;
203 }
204
SendRequest(const HttpRequestHeaders & request_headers,HttpResponseInfo * response,CompletionOnceCallback callback)205 int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
206 HttpResponseInfo* response,
207 CompletionOnceCallback callback) {
208 if (stream_closed_) {
209 return closed_stream_status_;
210 }
211
212 base::Time request_time = base::Time::Now();
213 CHECK(stream_);
214
215 stream_->SetRequestTime(request_time);
216 // This should only get called in the case of a request occurring
217 // during server push that has already begun but hasn't finished,
218 // so we set the response's request time to be the actual one
219 if (response_info_)
220 response_info_->request_time = request_time;
221
222 CHECK(!request_body_buf_.get());
223 if (HasUploadData()) {
224 request_body_buf_ =
225 base::MakeRefCounted<IOBufferWithSize>(kRequestBodyBufferSize);
226 // The request body buffer is empty at first.
227 request_body_buf_size_ = 0;
228 }
229
230 CHECK(!callback.is_null());
231 CHECK(response);
232 DCHECK(!response_info_);
233
234 response_info_ = response;
235
236 // Put the peer's IP address and port into the response.
237 IPEndPoint address;
238 int result = stream_->GetPeerAddress(&address);
239 if (result != OK)
240 return result;
241 response_info_->remote_endpoint = address;
242
243 spdy::Http2HeaderBlock headers;
244 CreateSpdyHeadersFromHttpRequest(*request_info_, priority_, request_headers,
245 &headers);
246 DispatchRequestHeadersCallback(headers);
247
248 bool will_send_data =
249 HasUploadData() || spdy_session_->EndStreamWithDataFrame();
250 result = stream_->SendRequestHeaders(
251 std::move(headers),
252 will_send_data ? MORE_DATA_TO_SEND : NO_MORE_DATA_TO_SEND);
253
254 if (result == ERR_IO_PENDING) {
255 CHECK(request_callback_.is_null());
256 request_callback_ = std::move(callback);
257 }
258 return result;
259 }
260
Cancel()261 void SpdyHttpStream::Cancel() {
262 request_callback_.Reset();
263 response_callback_.Reset();
264 if (stream_) {
265 stream_->Cancel(ERR_ABORTED);
266 DCHECK(!stream_);
267 }
268 }
269
OnHeadersSent()270 void SpdyHttpStream::OnHeadersSent() {
271 if (HasUploadData()) {
272 ReadAndSendRequestBodyData();
273 } else if (spdy_session_->EndStreamWithDataFrame()) {
274 SendEmptyBody();
275 } else {
276 MaybePostRequestCallback(OK);
277 }
278 }
279
OnEarlyHintsReceived(const spdy::Http2HeaderBlock & headers)280 void SpdyHttpStream::OnEarlyHintsReceived(
281 const spdy::Http2HeaderBlock& headers) {
282 DCHECK(!response_headers_complete_);
283 DCHECK(response_info_);
284 DCHECK_EQ(stream_->type(), SPDY_REQUEST_RESPONSE_STREAM);
285
286 const int rv = SpdyHeadersToHttpResponse(headers, response_info_);
287 CHECK_NE(rv, ERR_INCOMPLETE_HTTP2_HEADERS);
288
289 if (!response_callback_.is_null()) {
290 DoResponseCallback(OK);
291 }
292 }
293
OnHeadersReceived(const spdy::Http2HeaderBlock & response_headers)294 void SpdyHttpStream::OnHeadersReceived(
295 const spdy::Http2HeaderBlock& response_headers) {
296 DCHECK(!response_headers_complete_);
297 DCHECK(response_info_);
298 response_headers_complete_ = true;
299
300 const int rv = SpdyHeadersToHttpResponse(response_headers, response_info_);
301 DCHECK_NE(rv, ERR_INCOMPLETE_HTTP2_HEADERS);
302
303 if (rv == ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION) {
304 // Cancel will call OnClose, which might call callbacks and might destroy
305 // `this`.
306 stream_->Cancel(rv);
307 return;
308 }
309
310 response_info_->response_time = 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 spdy::Http2HeaderBlock & trailers)356 void SpdyHttpStream::OnTrailers(const spdy::Http2HeaderBlock& 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