stream_url_request_job.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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.
4
5#include "content/browser/streams/stream_url_request_job.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "content/browser/streams/stream.h"
9#include "net/base/io_buffer.h"
10#include "net/base/net_errors.h"
11#include "net/http/http_byte_range.h"
12#include "net/http/http_response_headers.h"
13#include "net/http/http_response_info.h"
14#include "net/http/http_util.h"
15#include "net/url_request/url_request.h"
16
17namespace content {
18
19StreamURLRequestJob::StreamURLRequestJob(
20    net::URLRequest* request,
21    net::NetworkDelegate* network_delegate,
22    scoped_refptr<Stream> stream)
23    : net::URLRequestJob(request, network_delegate),
24      weak_factory_(this),
25      stream_(stream),
26      headers_set_(false),
27      pending_buffer_size_(0),
28      total_bytes_read_(0),
29      max_range_(0),
30      request_failed_(false) {
31  DCHECK(stream_.get());
32  stream_->SetReadObserver(this);
33}
34
35StreamURLRequestJob::~StreamURLRequestJob() {
36  ClearStream();
37}
38
39void StreamURLRequestJob::OnDataAvailable(Stream* stream) {
40  // Clear the IO_PENDING status.
41  SetStatus(net::URLRequestStatus());
42  // Do nothing if pending_buffer_ is empty, i.e. there's no ReadRawData()
43  // operation waiting for IO completion.
44  if (!pending_buffer_.get())
45    return;
46
47  // pending_buffer_ is set to the IOBuffer instance provided to ReadRawData()
48  // by URLRequestJob.
49
50  int bytes_read;
51  switch (stream_->ReadRawData(
52      pending_buffer_.get(), pending_buffer_size_, &bytes_read)) {
53    case Stream::STREAM_HAS_DATA:
54      DCHECK_GT(bytes_read, 0);
55      break;
56    case Stream::STREAM_COMPLETE:
57      // Ensure this. Calling NotifyReadComplete call with 0 signals
58      // completion.
59      bytes_read = 0;
60      break;
61    case Stream::STREAM_EMPTY:
62      NOTREACHED();
63      break;
64    case Stream::STREAM_ABORTED:
65      // Handle this as connection reset.
66      NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
67                                       net::ERR_CONNECTION_RESET));
68      break;
69  }
70
71  // Clear the buffers before notifying the read is complete, so that it is
72  // safe for the observer to read.
73  pending_buffer_ = NULL;
74  pending_buffer_size_ = 0;
75
76  total_bytes_read_ += bytes_read;
77  NotifyReadComplete(bytes_read);
78}
79
80// net::URLRequestJob methods.
81void StreamURLRequestJob::Start() {
82  // Continue asynchronously.
83  base::MessageLoop::current()->PostTask(
84      FROM_HERE,
85      base::Bind(&StreamURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
86}
87
88void StreamURLRequestJob::Kill() {
89  net::URLRequestJob::Kill();
90  weak_factory_.InvalidateWeakPtrs();
91  ClearStream();
92}
93
94bool StreamURLRequestJob::ReadRawData(net::IOBuffer* buf,
95                                      int buf_size,
96                                      int* bytes_read) {
97  if (request_failed_)
98    return true;
99
100  DCHECK(buf);
101  DCHECK(bytes_read);
102  int to_read = buf_size;
103  if (max_range_ && to_read) {
104    if (to_read + total_bytes_read_ > max_range_)
105      to_read = max_range_ - total_bytes_read_;
106
107    if (to_read <= 0) {
108      *bytes_read = 0;
109      return true;
110    }
111  }
112
113  switch (stream_->ReadRawData(buf, to_read, bytes_read)) {
114    case Stream::STREAM_HAS_DATA:
115    case Stream::STREAM_COMPLETE:
116      total_bytes_read_ += *bytes_read;
117      return true;
118    case Stream::STREAM_EMPTY:
119      pending_buffer_ = buf;
120      pending_buffer_size_ = to_read;
121      SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
122      return false;
123    case Stream::STREAM_ABORTED:
124      // Handle this as connection reset.
125      NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
126                                       net::ERR_CONNECTION_RESET));
127      return false;
128  }
129  NOTREACHED();
130  return false;
131}
132
133bool StreamURLRequestJob::GetMimeType(std::string* mime_type) const {
134  if (!response_info_)
135    return false;
136
137  // TODO(zork): Support registered MIME types if needed.
138  return response_info_->headers->GetMimeType(mime_type);
139}
140
141void StreamURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
142  if (response_info_)
143    *info = *response_info_;
144}
145
146int StreamURLRequestJob::GetResponseCode() const {
147  if (!response_info_)
148    return -1;
149
150  return response_info_->headers->response_code();
151}
152
153void StreamURLRequestJob::SetExtraRequestHeaders(
154    const net::HttpRequestHeaders& headers) {
155  std::string range_header;
156  if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
157    std::vector<net::HttpByteRange> ranges;
158    if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
159      if (ranges.size() == 1) {
160        // Streams don't support seeking, so a non-zero starting position
161        // doesn't make sense.
162        if (ranges[0].first_byte_position() == 0) {
163          max_range_ = ranges[0].last_byte_position() + 1;
164        } else {
165          NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
166          return;
167        }
168      } else {
169        NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
170        return;
171      }
172    }
173  }
174}
175
176void StreamURLRequestJob::DidStart() {
177  // We only support GET request.
178  if (request()->method() != "GET") {
179    NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
180    return;
181  }
182
183  HeadersCompleted(net::HTTP_OK);
184}
185
186void StreamURLRequestJob::NotifyFailure(int error_code) {
187  request_failed_ = true;
188
189  // If we already return the headers on success, we can't change the headers
190  // now. Instead, we just error out.
191  if (headers_set_) {
192    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
193                                     error_code));
194    return;
195  }
196
197  // TODO(zork): Share these with BlobURLRequestJob.
198  net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
199  switch (error_code) {
200    case net::ERR_ACCESS_DENIED:
201      status_code = net::HTTP_FORBIDDEN;
202      break;
203    case net::ERR_FILE_NOT_FOUND:
204      status_code = net::HTTP_NOT_FOUND;
205      break;
206    case net::ERR_METHOD_NOT_SUPPORTED:
207      status_code = net::HTTP_METHOD_NOT_ALLOWED;
208      break;
209    case net::ERR_FAILED:
210      break;
211    default:
212      DCHECK(false);
213      break;
214  }
215  HeadersCompleted(status_code);
216}
217
218void StreamURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
219  std::string status("HTTP/1.1 ");
220  status.append(base::IntToString(status_code));
221  status.append(" ");
222  status.append(net::GetHttpReasonPhrase(status_code));
223  status.append("\0\0", 2);
224  net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
225
226  if (status_code == net::HTTP_OK) {
227    std::string content_type_header(net::HttpRequestHeaders::kContentType);
228    content_type_header.append(": ");
229    content_type_header.append("text/plain");
230    headers->AddHeader(content_type_header);
231  }
232
233  response_info_.reset(new net::HttpResponseInfo());
234  response_info_->headers = headers;
235
236  headers_set_ = true;
237
238  NotifyHeadersComplete();
239}
240
241void StreamURLRequestJob::ClearStream() {
242  if (stream_.get()) {
243    stream_->RemoveReadObserver(this);
244    stream_ = NULL;
245  }
246}
247
248}  // namespace content
249