1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/streams/stream_url_request_job.h"
7 #include "base/location.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/thread_task_runner_handle.h"
11 #include "content/browser/streams/stream.h"
12 #include "net/base/io_buffer.h"
13 #include "net/base/net_errors.h"
14 #include "net/http/http_byte_range.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/http/http_response_info.h"
17 #include "net/http/http_util.h"
18 #include "net/url_request/url_request.h"
22 StreamURLRequestJob::StreamURLRequestJob(
23 net::URLRequest
* request
,
24 net::NetworkDelegate
* network_delegate
,
25 scoped_refptr
<Stream
> stream
)
26 : net::URLRequestJob(request
, network_delegate
),
29 pending_buffer_size_(0),
32 request_failed_(false),
34 DCHECK(stream_
.get());
35 stream_
->SetReadObserver(this);
38 StreamURLRequestJob::~StreamURLRequestJob() {
42 void StreamURLRequestJob::OnDataAvailable(Stream
* stream
) {
43 // Clear the IO_PENDING status.
44 SetStatus(net::URLRequestStatus());
45 // Do nothing if pending_buffer_ is empty, i.e. there's no ReadRawData()
46 // operation waiting for IO completion.
47 if (!pending_buffer_
.get())
50 // pending_buffer_ is set to the IOBuffer instance provided to ReadRawData()
54 switch (stream_
->ReadRawData(
55 pending_buffer_
.get(), pending_buffer_size_
, &bytes_read
)) {
56 case Stream::STREAM_HAS_DATA
:
57 DCHECK_GT(bytes_read
, 0);
59 case Stream::STREAM_COMPLETE
:
60 // Ensure this. Calling NotifyReadComplete call with 0 signals
64 case Stream::STREAM_EMPTY
:
67 case Stream::STREAM_ABORTED
:
68 // Handle this as connection reset.
69 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
70 net::ERR_CONNECTION_RESET
));
74 // Clear the buffers before notifying the read is complete, so that it is
75 // safe for the observer to read.
76 pending_buffer_
= NULL
;
77 pending_buffer_size_
= 0;
79 total_bytes_read_
+= bytes_read
;
80 NotifyReadComplete(bytes_read
);
83 // net::URLRequestJob methods.
84 void StreamURLRequestJob::Start() {
85 // Continue asynchronously.
86 base::ThreadTaskRunnerHandle::Get()->PostTask(
88 base::Bind(&StreamURLRequestJob::DidStart
, weak_factory_
.GetWeakPtr()));
91 void StreamURLRequestJob::Kill() {
92 net::URLRequestJob::Kill();
93 weak_factory_
.InvalidateWeakPtrs();
97 bool StreamURLRequestJob::ReadRawData(net::IOBuffer
* buf
,
105 int to_read
= buf_size
;
106 if (max_range_
&& to_read
) {
107 if (to_read
+ total_bytes_read_
> max_range_
)
108 to_read
= max_range_
- total_bytes_read_
;
116 switch (stream_
->ReadRawData(buf
, to_read
, bytes_read
)) {
117 case Stream::STREAM_HAS_DATA
:
118 case Stream::STREAM_COMPLETE
:
119 total_bytes_read_
+= *bytes_read
;
121 case Stream::STREAM_EMPTY
:
122 pending_buffer_
= buf
;
123 pending_buffer_size_
= to_read
;
124 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING
, 0));
126 case Stream::STREAM_ABORTED
:
127 // Handle this as connection reset.
128 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
129 net::ERR_CONNECTION_RESET
));
136 bool StreamURLRequestJob::GetMimeType(std::string
* mime_type
) const {
140 // TODO(zork): Support registered MIME types if needed.
141 return response_info_
->headers
->GetMimeType(mime_type
);
144 void StreamURLRequestJob::GetResponseInfo(net::HttpResponseInfo
* info
) {
146 *info
= *response_info_
;
149 int StreamURLRequestJob::GetResponseCode() const {
153 return response_info_
->headers
->response_code();
156 void StreamURLRequestJob::SetExtraRequestHeaders(
157 const net::HttpRequestHeaders
& headers
) {
158 std::string range_header
;
159 if (headers
.GetHeader(net::HttpRequestHeaders::kRange
, &range_header
)) {
160 std::vector
<net::HttpByteRange
> ranges
;
161 if (net::HttpUtil::ParseRangeHeader(range_header
, &ranges
)) {
162 if (ranges
.size() == 1) {
163 // Streams don't support seeking, so a non-zero starting position
164 // doesn't make sense.
165 if (ranges
[0].first_byte_position() == 0) {
166 max_range_
= ranges
[0].last_byte_position() + 1;
168 NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED
);
172 NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED
);
179 void StreamURLRequestJob::DidStart() {
180 // We only support GET request.
181 if (request()->method() != "GET") {
182 NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED
);
186 HeadersCompleted(net::HTTP_OK
);
189 void StreamURLRequestJob::NotifyFailure(int error_code
) {
190 request_failed_
= true;
192 // If we already return the headers on success, we can't change the headers
193 // now. Instead, we just error out.
195 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
200 // TODO(zork): Share these with BlobURLRequestJob.
201 net::HttpStatusCode status_code
= net::HTTP_INTERNAL_SERVER_ERROR
;
202 switch (error_code
) {
203 case net::ERR_ACCESS_DENIED
:
204 status_code
= net::HTTP_FORBIDDEN
;
206 case net::ERR_FILE_NOT_FOUND
:
207 status_code
= net::HTTP_NOT_FOUND
;
209 case net::ERR_METHOD_NOT_SUPPORTED
:
210 status_code
= net::HTTP_METHOD_NOT_ALLOWED
;
212 case net::ERR_FAILED
:
218 HeadersCompleted(status_code
);
221 void StreamURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code
) {
222 std::string
status("HTTP/1.1 ");
223 status
.append(base::IntToString(status_code
));
225 status
.append(net::GetHttpReasonPhrase(status_code
));
226 status
.append("\0\0", 2);
227 net::HttpResponseHeaders
* headers
= new net::HttpResponseHeaders(status
);
229 if (status_code
== net::HTTP_OK
) {
230 std::string
content_type_header(net::HttpRequestHeaders::kContentType
);
231 content_type_header
.append(": ");
232 content_type_header
.append("text/plain");
233 headers
->AddHeader(content_type_header
);
236 response_info_
.reset(new net::HttpResponseInfo());
237 response_info_
->headers
= headers
;
241 NotifyHeadersComplete();
244 void StreamURLRequestJob::ClearStream() {
246 stream_
->RemoveReadObserver(this);
251 } // namespace content