Chromium Code Reviews| Index: components/cronet/ios/cronet_bidirectional_stream.cc |
| diff --git a/components/cronet/ios/cronet_bidirectional_stream.cc b/components/cronet/ios/cronet_bidirectional_stream.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c475275226206a6ad2d043c92501ce23ff651824 |
| --- /dev/null |
| +++ b/components/cronet/ios/cronet_bidirectional_stream.cc |
| @@ -0,0 +1,262 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "components/cronet/ios/cronet_bidirectional_stream.h" |
| + |
| +#include <stdbool.h> |
|
xunjieli
2016/04/08 14:43:15
Why do we need to include stdbool?
mef
2016/04/08 16:43:03
I think this is carry-over from when it used to be
|
| +#include <string> |
| +#include <vector> |
| + |
| +#include "base/bind.h" |
| +#include "base/location.h" |
| +#include "base/logging.h" |
| +#include "base/macros.h" |
|
xunjieli
2016/04/08 14:43:15
not needed?
mef
2016/04/08 16:43:02
Done.
|
| +#include "base/memory/ref_counted.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "components/cronet/ios/cronet_environment.h" |
| +#include "net/base/io_buffer.h" |
| +#include "net/base/net_errors.h" |
| +#include "net/base/request_priority.h" |
| +#include "net/http/bidirectional_stream.h" |
| +#include "net/http/bidirectional_stream_request_info.h" |
| +#include "net/http/http_network_session.h" |
| +#include "net/http/http_response_headers.h" |
| +#include "net/http/http_status_code.h" |
| +#include "net/http/http_transaction_factory.h" |
|
xunjieli
2016/04/08 14:43:15
Not needed?
mef
2016/04/08 16:43:03
Needed.
xunjieli
2016/04/08 17:46:15
Acknowledged.
|
| +#include "net/http/http_util.h" |
| +#include "net/spdy/spdy_header_block.h" |
| +#include "net/ssl/ssl_info.h" |
| +#include "net/url_request/http_user_agent_settings.h" |
| +#include "net/url_request/url_request_context.h" |
| +#include "url/gurl.h" |
| + |
| +namespace cronet { |
| + |
| +CronetBidirectionalStream::CronetBidirectionalStream( |
| + CronetEnvironment* environment, |
| + Delegate* delegate) |
| + : read_state_(NOT_STARTED), |
| + write_state_(NOT_STARTED), |
| + write_end_of_stream_(false), |
| + environment_(environment), |
| + delegate_(delegate) {} |
| + |
| +CronetBidirectionalStream::~CronetBidirectionalStream() { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| +} |
| + |
| +int CronetBidirectionalStream::Start(const char* url, |
| + int priority, |
| + const char* method, |
| + const net::HttpRequestHeaders& headers, |
| + bool end_of_stream) { |
| + // Prepare request info here to be able to return the error. |
| + scoped_ptr<net::BidirectionalStreamRequestInfo> request_info( |
| + new net::BidirectionalStreamRequestInfo()); |
| + request_info->url = GURL(url); |
| + request_info->priority = static_cast<net::RequestPriority>(priority); |
| + // Http method is a token, just as header name. |
| + request_info->method = method; |
| + if (!net::HttpUtil::IsValidHeaderName(request_info->method)) |
| + return -1; |
| + request_info->extra_headers.CopyFrom(headers); |
| + request_info->end_stream_on_headers = end_of_stream; |
| + write_end_of_stream_ = end_of_stream; |
| + DCHECK(environment_); |
| + environment_->PostToNetworkThread( |
| + FROM_HERE, |
| + base::Bind(&CronetBidirectionalStream::StartOnNetworkThread, |
| + base::Unretained(this), base::Passed(&request_info))); |
| + return 0; |
| +} |
| + |
| +bool CronetBidirectionalStream::ReadData(char* buffer, int capacity) { |
| + if (!buffer) |
| + return false; |
| + scoped_refptr<net::WrappedIOBuffer> read_buffer( |
| + new net::WrappedIOBuffer(buffer)); |
|
xunjieli
2016/04/08 14:43:15
Should we write to |read_buffer_| here, and use th
mef
2016/04/08 16:43:02
I don't think so. By accessing |read_buffer_| only
xunjieli
2016/04/08 17:46:15
Acknowledged.
|
| + |
| + environment_->PostToNetworkThread( |
| + FROM_HERE, base::Bind(&CronetBidirectionalStream::ReadDataOnNetworkThread, |
| + base::Unretained(this), read_buffer, capacity)); |
| + return true; |
| +} |
| + |
| +bool CronetBidirectionalStream::WriteData(const char* buffer, |
| + int count, |
| + bool end_of_stream) { |
| + if (!buffer) |
| + return false; |
| + |
| + scoped_refptr<net::WrappedIOBuffer> write_buffer( |
| + new net::WrappedIOBuffer(buffer)); |
|
xunjieli
2016/04/08 14:43:15
Should we write to |write_buffer_| here, and use t
mef
2016/04/08 16:43:03
ditto.
|
| + |
| + environment_->PostToNetworkThread( |
| + FROM_HERE, |
| + base::Bind(&CronetBidirectionalStream::WriteDataOnNetworkThread, |
| + base::Unretained(this), write_buffer, count, end_of_stream)); |
| + return true; |
| +} |
| + |
| +void CronetBidirectionalStream::Cancel() { |
| + environment_->PostToNetworkThread( |
| + FROM_HERE, base::Bind(&CronetBidirectionalStream::CancelOnNetworkThread, |
| + base::Unretained(this))); |
| +} |
| + |
| +void CronetBidirectionalStream::Destroy() { |
| + // Destroy could be called from any thread, including network thread (if |
| + // posting task to executor throws an exception), but is posted, so |this| |
| + // is valid until calling task is complete. |
| + environment_->PostToNetworkThread( |
| + FROM_HERE, base::Bind(&CronetBidirectionalStream::DestroyOnNetworkThread, |
| + base::Unretained(this))); |
| +} |
| + |
| +void CronetBidirectionalStream::OnHeadersSent() { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(write_state_ == STARTED); |
| + write_state_ = WAITING_FOR_WRITE; |
| + if (write_end_of_stream_) |
| + write_state_ = WRITING_DONE; |
| + delegate_->OnHeadersSent(); |
| +} |
| + |
| +void CronetBidirectionalStream::OnHeadersReceived( |
| + const net::SpdyHeaderBlock& response_headers) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(read_state_ == STARTED); |
| + read_state_ = WAITING_FOR_READ; |
| + // Get http status code from response headers. |
| + int http_status_code = 0; |
| + const auto http_status_header = response_headers.find(":status"); |
| + if (http_status_header != response_headers.end()) |
| + base::StringToInt(http_status_header->second, &http_status_code); |
| + const char* protocol = "unknown"; |
| + switch (bidi_stream_->GetProtocol()) { |
| + case net::kProtoHTTP2: |
| + protocol = "h2"; |
| + break; |
| + case net::kProtoQUIC1SPDY3: |
| + protocol = "quic/1+spdy/3"; |
| + break; |
| + default: |
| + break; |
| + } |
| + delegate_->OnHeadersReceived(response_headers, protocol); |
| +} |
| + |
| +void CronetBidirectionalStream::OnDataRead(int bytes_read) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(read_state_ == READING); |
| + read_state_ = WAITING_FOR_READ; |
| + delegate_->OnDataRead(read_buffer_->data(), bytes_read); |
| + |
| + // Free the read buffer. |
| + read_buffer_ = nullptr; |
| + if (bytes_read == 0) |
| + read_state_ = READING_DONE; |
| + MaybeOnSucceded(); |
| +} |
| + |
| +void CronetBidirectionalStream::OnDataSent() { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(write_state_ == WRITING); |
| + write_state_ = WAITING_FOR_WRITE; |
| + delegate_->OnDataSent(write_buffer_->data()); |
| + // Free the write buffer. |
| + write_buffer_ = nullptr; |
| + if (write_end_of_stream_) |
| + write_state_ = WRITING_DONE; |
| + MaybeOnSucceded(); |
| +} |
| + |
| +void CronetBidirectionalStream::OnTrailersReceived( |
| + const net::SpdyHeaderBlock& response_trailers) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + delegate_->OnTrailersReceived(response_trailers); |
| +} |
| + |
| +void CronetBidirectionalStream::OnFailed(int error) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + read_state_ = write_state_ = ERROR; |
| + delegate_->OnFailed(error); |
| +} |
| + |
| +void CronetBidirectionalStream::StartOnNetworkThread( |
| + scoped_ptr<net::BidirectionalStreamRequestInfo> request_info) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(!bidi_stream_); |
| + DCHECK(environment_->GetURLRequestContext()); |
| + request_info->extra_headers.SetHeaderIfMissing( |
| + net::HttpRequestHeaders::kUserAgent, environment_->user_agent()); |
| + bidi_stream_.reset(new net::BidirectionalStream( |
| + std::move(request_info), environment_->GetURLRequestContext() |
| + ->http_transaction_factory() |
| + ->GetSession(), |
| + this)); |
| + DCHECK(read_state_ == NOT_STARTED && write_state_ == NOT_STARTED); |
| + read_state_ = write_state_ = STARTED; |
| +} |
| + |
| +void CronetBidirectionalStream::ReadDataOnNetworkThread( |
| + scoped_refptr<net::WrappedIOBuffer> read_buffer, |
| + int buffer_size) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(read_buffer); |
| + DCHECK(!read_buffer_); |
| + DCHECK(read_state_ == WAITING_FOR_READ); |
| + read_state_ = READING; |
| + |
| + read_buffer_ = read_buffer; |
| + |
| + int bytes_read = bidi_stream_->ReadData(read_buffer_.get(), buffer_size); |
| + // If IO is pending, wait for the BidirectionalStream to call OnDataRead. |
| + if (bytes_read == net::ERR_IO_PENDING) |
| + return; |
| + |
| + if (bytes_read < 0) { |
| + OnFailed(bytes_read); |
| + return; |
| + } |
| + OnDataRead(bytes_read); |
| +} |
| + |
| +void CronetBidirectionalStream::WriteDataOnNetworkThread( |
| + scoped_refptr<net::WrappedIOBuffer> write_buffer, |
| + int buffer_size, |
| + bool end_of_stream) { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + DCHECK(write_buffer); |
| + DCHECK(!write_buffer_); |
| + DCHECK(write_state_ == WAITING_FOR_WRITE); |
| + write_state_ = WRITING; |
| + write_end_of_stream_ = end_of_stream; |
| + |
| + write_buffer_ = write_buffer; |
| + bidi_stream_->SendData(write_buffer_.get(), buffer_size, end_of_stream); |
| +} |
| + |
| +void CronetBidirectionalStream::CancelOnNetworkThread() { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + read_state_ = write_state_ = CANCELED; |
| + bidi_stream_.reset(); |
| + delegate_->OnCanceled(); |
| +} |
| + |
| +void CronetBidirectionalStream::DestroyOnNetworkThread() { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + delete this; |
| +} |
| + |
| +void CronetBidirectionalStream::MaybeOnSucceded() { |
| + DCHECK(environment_->IsOnNetworkThread()); |
| + if (read_state_ == READING_DONE && write_state_ == WRITING_DONE) { |
| + read_state_ = write_state_ = SUCCESS; |
| + delegate_->OnSucceeded(); |
| + } |
| +} |
| + |
| +} // namespace cronet |