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