/******************************************************************************* * This file is part of RedReader. * * RedReader is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RedReader is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RedReader. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.quantumbadger.redreader.http.okhttp; import android.content.Context; import okhttp3.CacheControl; import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import org.quantumbadger.redreader.cache.CacheRequest; import org.quantumbadger.redreader.common.Constants; import org.quantumbadger.redreader.common.TorCommon; import org.quantumbadger.redreader.http.HTTPBackend; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class OKHTTPBackend extends HTTPBackend { private final OkHttpClient mClient; private static HTTPBackend httpBackend; private OKHTTPBackend() { final OkHttpClient.Builder builder = new OkHttpClient.Builder(); if(TorCommon.isTorEnabled()) { Proxy tor = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8118)); //SOCKS appears to be broken for now, Relevant: https://github.com/square/okhttp/issues/2315 builder.proxy(tor); } builder.followRedirects(true); builder.followSslRedirects(true); builder.connectTimeout(15000, TimeUnit.SECONDS); builder.readTimeout(10000, TimeUnit.SECONDS); builder.connectionPool(new ConnectionPool(1, 5, TimeUnit.SECONDS)); builder.retryOnConnectionFailure(true); mClient = builder.build(); } public static synchronized HTTPBackend getHttpBackend() { if(httpBackend == null) { httpBackend = new OKHTTPBackend(); } return httpBackend; } @Override public synchronized void recreateHttpBackend() { httpBackend = new OKHTTPBackend(); } @Override public Request prepareRequest(final Context context, final RequestDetails details) { final okhttp3.Request.Builder builder = new okhttp3.Request.Builder(); builder.header("User-Agent", Constants.ua(context)); final List<PostField> postFields = details.getPostFields(); if(postFields != null) { builder.post(RequestBody.create( MediaType.parse("application/x-www-form-urlencoded"), PostField.encodeList(postFields))); } else { builder.get(); } builder.url(details.getUrl().toString()); builder.cacheControl(CacheControl.FORCE_NETWORK); final AtomicReference<Call> callRef = new AtomicReference<>(); return new Request() { public void executeInThisThread(final Listener listener) { final Call call = mClient.newCall(builder.build()); callRef.set(call); try { final Response response; try { response = call.execute(); } catch(IOException e) { listener.onError(CacheRequest.REQUEST_FAILURE_CONNECTION, e, null); return; } final int status = response.code(); if(status == 200 || status == 202) { final ResponseBody body = response.body(); final InputStream bodyStream; final Long bodyBytes; if(body != null) { bodyStream = body.byteStream(); bodyBytes = body.contentLength(); } else { // TODO error bodyStream = null; bodyBytes = null; } final String contentType = response.header("Content-Type"); listener.onSuccess(contentType, bodyBytes, bodyStream); } else { listener.onError(CacheRequest.REQUEST_FAILURE_REQUEST, null, status); } } catch(Throwable t) { listener.onError(CacheRequest.REQUEST_FAILURE_CONNECTION, t, null); } } @Override public void cancel() { final Call call = callRef.getAndSet(null); if(call != null) { call.cancel(); } } @Override public void addHeader(final String name, final String value) { builder.addHeader(name, value); } }; } }