blob: 80340511ac7b2a71228d9107ccfdf2f4d304bec2 [file] [log] [blame]
// Copyright 2015 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.
package org.domokit.oknet;
import android.util.Log;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import org.chromium.base.TraceEvent;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.DataPipe;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojo.system.MojoResult;
import org.chromium.mojo.system.Pair;
import org.chromium.mojom.mojo.HttpHeader;
import org.chromium.mojom.mojo.NetworkError;
import org.chromium.mojom.mojo.UrlLoader;
import org.chromium.mojom.mojo.UrlLoaderStatus;
import org.chromium.mojom.mojo.UrlRequest;
import org.chromium.mojom.mojo.UrlResponse;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import okio.BufferedSource;
/**
* OkHttp implementation of UrlLoader.
*/
public class UrlLoaderImpl implements UrlLoader {
private static final String TAG = "UrlLoaderImpl";
private final Core mCore;
private final OkHttpClient mClient;
private final Executor mExecutor;
private boolean mIsLoading;
private NetworkError mError;
private static long sNextTracingId = 1;
private final long mTracingId;
class CopyToPipeJob implements Runnable {
private ResponseBody mBody;
private BufferedSource mSource;
private DataPipe.ProducerHandle mProducer;
public CopyToPipeJob(ResponseBody body, DataPipe.ProducerHandle producerHandle) {
mBody = body;
mSource = body.source();
mProducer = producerHandle;
}
@Override
public void run() {
TraceEvent.begin("UrlLoaderImpl::CopyToPipeJob::copy");
int result = 0;
do {
try {
ByteBuffer buffer = mProducer.beginWriteData(0, DataPipe.WriteFlags.none());
// TODO(abarth): There must be a way to do this without the temporary buffer.
byte[] tmp = new byte[buffer.capacity()];
result = mSource.read(tmp);
buffer.put(tmp);
mProducer.endWriteData(result == -1 ? 0 : result);
} catch (MojoException e) {
// No one read the pipe, they just closed it.
if (e.getMojoResult() == MojoResult.FAILED_PRECONDITION) {
break;
} else if (e.getMojoResult() == MojoResult.SHOULD_WAIT) {
mCore.wait(mProducer, Core.HandleSignals.WRITABLE, -1);
} else {
throw e;
}
} catch (IOException e) {
Log.e(TAG, "mSource.read failed", e);
break;
}
} while (result != -1);
mIsLoading = false;
mProducer.close();
try {
mBody.close();
} catch (IOException e) {
Log.e(TAG, "mBody.close failed", e);
}
TraceEvent.finishAsync("UrlLoaderImpl", mTracingId);
TraceEvent.end("UrlLoaderImpl::CopyToPipeJob::copy");
}
}
public UrlLoaderImpl(Core core, OkHttpClient client, Executor executor) {
assert core != null;
mCore = core;
mClient = client;
mExecutor = executor;
mIsLoading = false;
mError = null;
mTracingId = sNextTracingId++;
}
@Override
public void close() {}
@Override
public void onConnectionError(MojoException e) {}
@Override
public void start(UrlRequest request, StartResponse callback) {
TraceEvent.startAsync("UrlLoaderImpl", mTracingId);
mIsLoading = true;
mError = null;
URL url = null;
try {
url = new URL(request.url);
} catch (MalformedURLException e) {
Log.w(TAG, "Failed to parse url " + request.url);
mError = new NetworkError();
// TODO(abarth): Which mError.code should we set?
mError.description = e.toString();
UrlResponse urlResponse = new UrlResponse();
urlResponse.url = request.url;
urlResponse.error = mError;
callback.call(urlResponse);
return;
}
Request.Builder builder =
new Request.Builder().url(url).method(request.method, null);
if (request.headers != null) {
for (HttpHeader header : request.headers) {
builder.addHeader(header.name, header.value);
}
}
// TODO(abarth): body, responseBodyBufferSize, autoFollowRedirects, bypassCache.
final StartResponse responseCallback = callback;
Call call = mClient.newCall(builder.build());
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.w(TAG, "Network failure loading " + request.urlString());
mError = new NetworkError();
// TODO(abarth): Which mError.code should we set?
mError.description = e.toString();
UrlResponse urlResponse = new UrlResponse();
urlResponse.url = request.urlString();
urlResponse.error = mError;
responseCallback.call(urlResponse);
mIsLoading = false;
TraceEvent.finishAsync("UrlLoaderImpl", mTracingId);
}
@Override
public void onResponse(Response response) {
UrlResponse urlResponse = new UrlResponse();
urlResponse.url = response.request().urlString();
urlResponse.statusCode = response.code();
urlResponse.statusLine = response.message();
if (urlResponse.statusCode >= 400) {
Log.w(TAG, "Failed to load: " + urlResponse.url + " ("
+ urlResponse.statusCode + ")");
}
Headers headers = response.headers();
urlResponse.headers = new HttpHeader[headers.size()];
for (int i = 0; i < headers.size(); ++i) {
HttpHeader header = new HttpHeader();
header.name = headers.name(i);
header.value = headers.value(i);
urlResponse.headers[i] = header;
}
ResponseBody body = response.body();
MediaType mediaType = body.contentType();
if (mediaType != null) {
urlResponse.mimeType = mediaType.type() + "/" + mediaType.subtype();
Charset charset = mediaType.charset();
if (charset != null) {
urlResponse.charset = charset.displayName();
}
}
Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles =
mCore.createDataPipe(null);
DataPipe.ProducerHandle producerHandle = handles.first;
DataPipe.ConsumerHandle consumerHandle = handles.second;
urlResponse.body = consumerHandle;
responseCallback.call(urlResponse);
mExecutor.execute(new CopyToPipeJob(body, producerHandle));
}
});
}
@Override
public void followRedirect(FollowRedirectResponse callback) {
// TODO(abarth): Implement redirects.
callback.call(new UrlResponse());
}
@Override
public void queryStatus(QueryStatusResponse callback) {
UrlLoaderStatus status = new UrlLoaderStatus();
status.error = mError;
status.isLoading = mIsLoading;
callback.call(status);
}
}