android_stream_reader_url_request_job.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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 "android_webview/browser/net/android_stream_reader_url_request_job.h"
6
7#include <string>
8
9#include "android_webview/browser/input_stream.h"
10#include "android_webview/browser/net/input_stream_reader.h"
11#include "base/android/jni_android.h"
12#include "base/android/jni_string.h"
13#include "base/bind.h"
14#include "base/bind_helpers.h"
15#include "base/lazy_instance.h"
16#include "base/message_loop.h"
17#include "base/message_loop_proxy.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/task_runner.h"
20#include "base/threading/sequenced_worker_pool.h"
21#include "base/threading/thread.h"
22#include "content/public/browser/browser_thread.h"
23#include "net/base/io_buffer.h"
24#include "net/base/mime_util.h"
25#include "net/base/net_errors.h"
26#include "net/base/net_util.h"
27#include "net/http/http_response_headers.h"
28#include "net/http/http_response_info.h"
29#include "net/http/http_util.h"
30#include "net/url_request/url_request.h"
31#include "net/url_request/url_request_job_manager.h"
32
33using android_webview::InputStream;
34using android_webview::InputStreamReader;
35using base::android::AttachCurrentThread;
36using base::PostTaskAndReplyWithResult;
37using content::BrowserThread;
38
39namespace {
40
41const int kHTTPOk = 200;
42const int kHTTPNotFound = 404;
43
44const char kHTTPOkText[] = "OK";
45const char kHTTPNotFoundText[] = "Not Found";
46
47} // namespace
48
49// The requests posted to the worker thread might outlive the job.  Thread-safe
50// ref counting is used to ensure that the InputStream and InputStreamReader
51// members of this class are still there when the closure is run on the worker
52// thread.
53class InputStreamReaderWrapper :
54    public base::RefCountedThreadSafe<InputStreamReaderWrapper> {
55 public:
56  InputStreamReaderWrapper(
57      scoped_ptr<InputStream> input_stream,
58      scoped_ptr<InputStreamReader> input_stream_reader)
59      : input_stream_(input_stream.Pass()),
60        input_stream_reader_(input_stream_reader.Pass()) {
61    DCHECK(input_stream_);
62    DCHECK(input_stream_reader_);
63  }
64
65  android_webview::InputStream* input_stream() {
66    return input_stream_.get();
67  }
68
69  int Seek(const net::HttpByteRange& byte_range) {
70    return input_stream_reader_->Seek(byte_range);
71  }
72
73  int ReadRawData(net::IOBuffer* buffer, int buffer_size) {
74    return input_stream_reader_->ReadRawData(buffer, buffer_size);
75  }
76
77 private:
78  friend class base::RefCountedThreadSafe<InputStreamReaderWrapper>;
79  ~InputStreamReaderWrapper() {}
80
81  scoped_ptr<android_webview::InputStream> input_stream_;
82  scoped_ptr<android_webview::InputStreamReader> input_stream_reader_;
83
84  DISALLOW_COPY_AND_ASSIGN(InputStreamReaderWrapper);
85};
86
87AndroidStreamReaderURLRequestJob::AndroidStreamReaderURLRequestJob(
88    net::URLRequest* request,
89    net::NetworkDelegate* network_delegate,
90    scoped_ptr<Delegate> delegate)
91    : URLRequestJob(request, network_delegate),
92      delegate_(delegate.Pass()),
93      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
94  DCHECK(delegate_);
95}
96
97AndroidStreamReaderURLRequestJob::~AndroidStreamReaderURLRequestJob() {
98}
99
100namespace {
101
102typedef base::Callback<
103    void(scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>,
104         scoped_ptr<InputStream>)> OnInputStreamOpenedCallback;
105
106// static
107void OpenInputStreamOnWorkerThread(
108    scoped_refptr<base::MessageLoopProxy> job_thread_proxy,
109    scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate> delegate,
110    const GURL& url,
111    OnInputStreamOpenedCallback callback) {
112
113  JNIEnv* env = AttachCurrentThread();
114  DCHECK(env);
115
116  scoped_ptr<InputStream> input_stream = delegate->OpenInputStream(env, url);
117  job_thread_proxy->PostTask(FROM_HERE,
118                             base::Bind(callback,
119                                        base::Passed(delegate.Pass()),
120                                        base::Passed(input_stream.Pass())));
121}
122
123} // namespace
124
125void AndroidStreamReaderURLRequestJob::Start() {
126  DCHECK(thread_checker_.CalledOnValidThread());
127  // Start reading asynchronously so that all error reporting and data
128  // callbacks happen as they would for network requests.
129  SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING,
130                                  net::ERR_IO_PENDING));
131
132  // This could be done in the InputStreamReader but would force more
133  // complex synchronization in the delegate.
134  GetWorkerThreadRunner()->PostTask(
135      FROM_HERE,
136      base::Bind(
137          &OpenInputStreamOnWorkerThread,
138          MessageLoop::current()->message_loop_proxy(),
139          // This is intentional - the job could be deleted while the callback
140          // is executing on the background thread.
141          // The delegate will be "returned" to the job once the InputStream
142          // open attempt is completed.
143          base::Passed(&delegate_),
144          request()->url(),
145          base::Bind(&AndroidStreamReaderURLRequestJob::OnInputStreamOpened,
146                     weak_factory_.GetWeakPtr())));
147}
148
149void AndroidStreamReaderURLRequestJob::Kill() {
150  DCHECK(thread_checker_.CalledOnValidThread());
151  weak_factory_.InvalidateWeakPtrs();
152  URLRequestJob::Kill();
153}
154
155scoped_ptr<InputStreamReader>
156AndroidStreamReaderURLRequestJob::CreateStreamReader(InputStream* stream) {
157  return make_scoped_ptr(new InputStreamReader(stream));
158}
159
160void AndroidStreamReaderURLRequestJob::OnInputStreamOpened(
161      scoped_ptr<Delegate> returned_delegate,
162      scoped_ptr<android_webview::InputStream> input_stream) {
163  DCHECK(thread_checker_.CalledOnValidThread());
164  DCHECK(returned_delegate);
165  delegate_ = returned_delegate.Pass();
166
167  if (!input_stream) {
168    bool restart_required = false;
169    delegate_->OnInputStreamOpenFailed(request(), &restart_required);
170    if (restart_required) {
171      NotifyRestartRequired();
172    } else {
173      // Clear the IO_PENDING status set in Start().
174      SetStatus(net::URLRequestStatus());
175      HeadersComplete(kHTTPNotFound, kHTTPNotFoundText);
176    }
177    return;
178  }
179
180  scoped_ptr<InputStreamReader> input_stream_reader(
181      CreateStreamReader(input_stream.get()));
182  DCHECK(input_stream_reader);
183
184  DCHECK(!input_stream_reader_wrapper_);
185  input_stream_reader_wrapper_ = new InputStreamReaderWrapper(
186      input_stream.Pass(), input_stream_reader.Pass());
187
188  PostTaskAndReplyWithResult(
189      GetWorkerThreadRunner(),
190      FROM_HERE,
191      base::Bind(&InputStreamReaderWrapper::Seek,
192                 input_stream_reader_wrapper_,
193                 byte_range_),
194      base::Bind(&AndroidStreamReaderURLRequestJob::OnReaderSeekCompleted,
195                 weak_factory_.GetWeakPtr()));
196}
197
198void AndroidStreamReaderURLRequestJob::OnReaderSeekCompleted(int result) {
199  DCHECK(thread_checker_.CalledOnValidThread());
200  // Clear the IO_PENDING status set in Start().
201  SetStatus(net::URLRequestStatus());
202  if (result >= 0) {
203    set_expected_content_size(result);
204    HeadersComplete(kHTTPOk, kHTTPOkText);
205  } else {
206    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
207  }
208}
209
210void AndroidStreamReaderURLRequestJob::OnReaderReadCompleted(int result) {
211  DCHECK(thread_checker_.CalledOnValidThread());
212  // The URLRequest API contract requires that:
213  // * NotifyDone be called once, to set the status code, indicate the job is
214  //   finished (there will be no further IO),
215  // * NotifyReadComplete be called if false is returned from ReadRawData to
216  //   indicate that the IOBuffer will not be used by the job anymore.
217  // There might be multiple calls to ReadRawData (and thus multiple calls to
218  // NotifyReadComplete), which is why NotifyDone is called only on errors
219  // (result < 0) and end of data (result == 0).
220  if (result == 0) {
221    NotifyDone(net::URLRequestStatus());
222  } else if (result < 0) {
223    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
224  } else {
225    // Clear the IO_PENDING status.
226    SetStatus(net::URLRequestStatus());
227  }
228  NotifyReadComplete(result);
229}
230
231base::TaskRunner* AndroidStreamReaderURLRequestJob::GetWorkerThreadRunner() {
232  return static_cast<base::TaskRunner*>(BrowserThread::GetBlockingPool());
233}
234
235bool AndroidStreamReaderURLRequestJob::ReadRawData(net::IOBuffer* dest,
236                                                   int dest_size,
237                                                   int* bytes_read) {
238  DCHECK(thread_checker_.CalledOnValidThread());
239  if (!input_stream_reader_wrapper_) {
240    // This will happen if opening the InputStream fails in which case the
241    // error is communicated by setting the HTTP response status header rather
242    // than failing the request during the header fetch phase.
243    *bytes_read = 0;
244    return true;
245  }
246
247  PostTaskAndReplyWithResult(
248      GetWorkerThreadRunner(),
249      FROM_HERE,
250      base::Bind(&InputStreamReaderWrapper::ReadRawData,
251                 input_stream_reader_wrapper_,
252                 make_scoped_refptr(dest),
253                 dest_size),
254      base::Bind(&AndroidStreamReaderURLRequestJob::OnReaderReadCompleted,
255                 weak_factory_.GetWeakPtr()));
256
257  SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING,
258                                  net::ERR_IO_PENDING));
259  return false;
260}
261
262bool AndroidStreamReaderURLRequestJob::GetMimeType(
263    std::string* mime_type) const {
264  DCHECK(thread_checker_.CalledOnValidThread());
265  JNIEnv* env = AttachCurrentThread();
266  DCHECK(env);
267
268  if (!input_stream_reader_wrapper_)
269    return false;
270
271  // Since it's possible for this call to alter the InputStream a
272  // Seek or ReadRawData operation running in the background is not permitted.
273  DCHECK(!request_->status().is_io_pending());
274
275  return delegate_->GetMimeType(
276      env, request(), input_stream_reader_wrapper_->input_stream(), mime_type);
277}
278
279bool AndroidStreamReaderURLRequestJob::GetCharset(std::string* charset) {
280  DCHECK(thread_checker_.CalledOnValidThread());
281  JNIEnv* env = AttachCurrentThread();
282  DCHECK(env);
283
284  if (!input_stream_reader_wrapper_)
285    return false;
286
287  // Since it's possible for this call to alter the InputStream a
288  // Seek or ReadRawData operation running in the background is not permitted.
289  DCHECK(!request_->status().is_io_pending());
290
291  return delegate_->GetCharset(
292      env, request(), input_stream_reader_wrapper_->input_stream(), charset);
293}
294
295void AndroidStreamReaderURLRequestJob::HeadersComplete(
296    int status_code,
297    const std::string& status_text) {
298  std::string status("HTTP/1.1 ");
299  status.append(base::IntToString(status_code));
300  status.append(" ");
301  status.append(status_text);
302  // HttpResponseHeaders expects its input string to be terminated by two NULs.
303  status.append("\0\0", 2);
304  net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
305
306  if (status_code == kHTTPOk) {
307    if (expected_content_size() != -1) {
308      std::string content_length_header(
309          net::HttpRequestHeaders::kContentLength);
310      content_length_header.append(": ");
311      content_length_header.append(
312          base::Int64ToString(expected_content_size()));
313      headers->AddHeader(content_length_header);
314    }
315
316    std::string mime_type;
317    if (GetMimeType(&mime_type) && !mime_type.empty()) {
318      std::string content_type_header(net::HttpRequestHeaders::kContentType);
319      content_type_header.append(": ");
320      content_type_header.append(mime_type);
321      headers->AddHeader(content_type_header);
322    }
323  }
324
325  response_info_.reset(new net::HttpResponseInfo());
326  response_info_->headers = headers;
327
328  NotifyHeadersComplete();
329}
330
331int AndroidStreamReaderURLRequestJob::GetResponseCode() const {
332  if (response_info_)
333    return response_info_->headers->response_code();
334  return URLRequestJob::GetResponseCode();
335}
336
337void AndroidStreamReaderURLRequestJob::GetResponseInfo(
338    net::HttpResponseInfo* info) {
339  if (response_info_)
340    *info = *response_info_;
341}
342
343void AndroidStreamReaderURLRequestJob::SetExtraRequestHeaders(
344    const net::HttpRequestHeaders& headers) {
345  std::string range_header;
346  if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
347    // We only extract the "Range" header so that we know how many bytes in the
348    // stream to skip and how many to read after that.
349    std::vector<net::HttpByteRange> ranges;
350    if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
351      if (ranges.size() == 1) {
352        byte_range_ = ranges[0];
353      } else {
354        // We don't support multiple range requests in one single URL request,
355        // because we need to do multipart encoding here.
356        NotifyDone(net::URLRequestStatus(
357            net::URLRequestStatus::FAILED,
358            net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
359      }
360    }
361  }
362}
363