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