/*
* Firetweet - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.getlantern.firetweet.util.net;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.net.Uri;
import com.nostra13.universalimageloader.utils.IoUtils;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request.Builder;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.Network;
import org.getlantern.firetweet.FiretweetConstants;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import javax.net.SocketFactory;
import okio.BufferedSink;
import twitter4j.TwitterException;
import twitter4j.auth.Authorization;
import twitter4j.http.HostAddressResolver;
import twitter4j.http.HttpClient;
import twitter4j.http.HttpClientConfiguration;
import twitter4j.http.HttpParameter;
import twitter4j.http.HttpRequest;
import twitter4j.http.HttpResponse;
import twitter4j.http.RequestMethod;
import com.crashlytics.android.Crashlytics;
/**
* Created by mariotaku on 15/1/22.
*/
public class OkHttpClientImpl implements HttpClient, FiretweetConstants {
public static final MediaType APPLICATION_FORM_URLENCODED = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8");
private final Context context;
private final HttpClientConfiguration conf;
private final OkHttpClient client;
private final HostAddressResolver resolver;
public OkHttpClientImpl(Context context, HttpClientConfiguration conf) {
this.context = context;
this.conf = conf;
this.resolver = conf.getHostAddressResolverFactory().getInstance(conf);
this.client = createHttpClient(conf);
}
@Override
public HttpResponse request(HttpRequest req) throws TwitterException {
final Builder builder = new Builder();
for (Entry<String, List<String>> headerEntry : req.getRequestHeaders().entrySet()) {
final String name = headerEntry.getKey();
for (String value : headerEntry.getValue()) {
builder.addHeader(name, value);
}
}
final Authorization authorization = req.getAuthorization();
if (authorization != null) {
final String authHeader = authorization.getAuthorizationHeader(req);
if (authHeader != null) {
builder.header("Authorization", authHeader);
}
}
Response response = null;
try {
setupRequestBuilder(builder, req);
response = client.newCall(builder.build()).execute();
return new OkHttpResponse(conf, null, response);
} catch (IOException e) {
Crashlytics.logException(e);
throw new TwitterException(e);
}
}
@Override
public void shutdown() {
}
private OkHttpClient createHttpClient(HttpClientConfiguration conf) {
final OkHttpClient client = new OkHttpClient();
final boolean ignoreSSLError = conf.isSSLErrorIgnored();
final SSLCertificateSocketFactory sslSocketFactory;
if (ignoreSSLError) {
sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getInsecure(0, null);
} else {
sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0, null);
}
// sslSocketFactory.setTrustManagers(new TrustManager[]{new FiretweetTrustManager(context)});
// client.setHostnameVerifier(new HostResolvedHostnameVerifier(context, ignoreSSLError));
client.setSslSocketFactory(sslSocketFactory);
client.setSocketFactory(SocketFactory.getDefault());
client.setConnectTimeout(conf.getHttpConnectionTimeout(), TimeUnit.MILLISECONDS);
if (conf.isProxyConfigured()) {
client.setProxy(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(conf.getHttpProxyHost(),
conf.getHttpProxyPort())));
}
Internal.instance.setNetwork(client, new Network() {
@Override
public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException {
try {
return resolver.resolve(host);
} catch (IOException e) {
Crashlytics.logException(e);
if (e instanceof UnknownHostException) throw (UnknownHostException) e;
throw new UnknownHostException("Unable to resolve address " + e.getMessage());
}
}
});
return client;
}
private RequestBody getRequestBody(HttpParameter[] params) throws IOException {
if (params == null) return null;
if (!HttpParameter.containsFile(params)) {
return RequestBody.create(APPLICATION_FORM_URLENCODED, HttpParameter.encodeParameters(params));
}
final MultipartBuilder builder = new MultipartBuilder();
builder.type(MultipartBuilder.FORM);
for (final HttpParameter param : params) {
if (param.isFile()) {
RequestBody requestBody;
if (param.hasFileBody()) {
requestBody = new StreamRequestBody(MediaType.parse(param.getContentType()), param.getFileBody(), true);
} else {
requestBody = RequestBody.create(MediaType.parse(param.getContentType()), param.getFile());
}
builder.addFormDataPart(param.getName(), param.getFileName(), requestBody);
} else {
builder.addFormDataPart(param.getName(), param.getValue());
}
}
return builder.build();
}
static class StreamRequestBody extends RequestBody {
private final MediaType contentType;
private final InputStream stream;
private final boolean closeAfterWrite;
StreamRequestBody(MediaType contentType, InputStream stream, boolean closeAfterWrite) {
this.contentType = contentType;
this.stream = stream;
this.closeAfterWrite = closeAfterWrite;
}
@Override
public MediaType contentType() {
return contentType;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
int len;
byte[] buf = new byte[8192];
while ((len = stream.read(buf)) != -1) {
sink.write(buf, 0, len);
}
if (closeAfterWrite) {
IoUtils.closeSilently(stream);
}
}
}
private void setupRequestBuilder(Builder builder, HttpRequest req) throws IOException {
final Uri.Builder uriBuilder = Uri.parse(req.getURL()).buildUpon();
final RequestMethod method = req.getMethod();
if (method != RequestMethod.POST && method != RequestMethod.PUT) {
final HttpParameter[] parameters = req.getParameters();
if (parameters != null) {
for (HttpParameter param : parameters) {
uriBuilder.appendQueryParameter(param.getName(), param.getValue());
}
}
}
final Uri uri = uriBuilder.build();
switch (req.getMethod()) {
case GET: {
builder.get();
break;
}
case POST: {
builder.post(getRequestBody(req.getParameters()));
break;
}
case DELETE: {
builder.delete();
break;
}
case HEAD: {
builder.head();
break;
}
case PUT: {
builder.put(getRequestBody(req.getParameters()));
break;
}
default: {
throw new AssertionError();
}
}
builder.url(uri.toString());
}
private static class OkHttpResponse extends HttpResponse {
private final Response response;
public OkHttpResponse(HttpClientConfiguration conf, HttpRequest request, Response response)
throws TwitterException, IOException {
super(conf);
this.response = response;
statusCode = response.code();
if ("gzip".equals(response.header("Content-Encoding"))) {
is = new GZIPInputStream(response.body().byteStream());
} else {
is = response.body().byteStream();
}
if (!response.isSuccessful()) {
throw new TwitterException(response.message(), request, this);
}
}
@Override
public void disconnect() throws IOException {
if (is != null) {
is.close();
}
}
@Override
public String getResponseHeader(String name) {
return response.header(name);
}
@Override
public Map<String, List<String>> getResponseHeaderFields() {
final Headers headers = response.headers();
final Map<String, List<String>> maps = new HashMap<>();
for (final String name : headers.names()) {
final List<String> values = new ArrayList<>(1);
for (final String value : headers.values(name)) {
values.add(value);
}
maps.put(name, values);
}
return maps;
}
@Override
public List<String> getResponseHeaders(String name) {
return response.headers(name);
}
}
}