Skip to content

Instantly share code, notes, and snippets.

@lexer
Created May 29, 2013 11:35
Show Gist options
  • Save lexer/5669648 to your computer and use it in GitHub Desktop.
Save lexer/5669648 to your computer and use it in GitHub Desktop.
Google Http Client Library OkHttp Transport
/*
* Copyright (c) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.api.client.http.okhttp;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.util.Preconditions;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
/**
* @author Yaniv Inbar
*/
final class OkHttpRequest extends LowLevelHttpRequest {
private final HttpURLConnection connection;
/**
* @param connection HTTP URL connection
*/
OkHttpRequest(HttpURLConnection connection) {
this.connection = connection;
connection.setInstanceFollowRedirects(false);
}
@Override
public void addHeader(String name, String value) {
connection.addRequestProperty(name, value);
}
@Override
public void setTimeout(int connectTimeout, int readTimeout) {
connection.setReadTimeout(readTimeout);
connection.setConnectTimeout(connectTimeout);
}
@Override
public LowLevelHttpResponse execute() throws IOException {
HttpURLConnection connection = this.connection;
// write content
if (getStreamingContent() != null) {
String contentType = getContentType();
if (contentType != null) {
addHeader("Content-Type", contentType);
}
String contentEncoding = getContentEncoding();
if (contentEncoding != null) {
addHeader("Content-Encoding", contentEncoding);
}
long contentLength = getContentLength();
if (contentLength >= 0) {
addHeader("Content-Length", Long.toString(contentLength));
}
String requestMethod = connection.getRequestMethod();
if ("POST".equals(requestMethod) || "PUT".equals(requestMethod)) {
connection.setDoOutput(true);
// see http://developer.android.com/reference/java/net/HttpURLConnection.html
if (contentLength >= 0 && contentLength <= Integer.MAX_VALUE) {
connection.setFixedLengthStreamingMode((int) contentLength);
} else {
connection.setChunkedStreamingMode(0);
}
OutputStream out = connection.getOutputStream();
try {
getStreamingContent().writeTo(out);
} finally {
out.close();
}
} else {
// cannot call setDoOutput(true) because it would change a GET method to POST
// for HEAD, OPTIONS, DELETE, or TRACE it would throw an exceptions
Preconditions.checkArgument(
contentLength == 0, "%s with non-zero content length is not supported", requestMethod);
}
}
// connect
boolean successfulConnection = false;
try {
connection.connect();
OkHttpResponse response = new OkHttpResponse(connection);
successfulConnection = true;
return response;
} finally {
if (!successfulConnection) {
connection.disconnect();
}
}
}
}
/*
* Copyright (c) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.api.client.http.okhttp;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.LowLevelHttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
final class OkHttpResponse extends LowLevelHttpResponse {
private final HttpURLConnection connection;
private final int responseCode;
private final String responseMessage;
private final ArrayList<String> headerNames = new ArrayList<String>();
private final ArrayList<String> headerValues = new ArrayList<String>();
OkHttpResponse(HttpURLConnection connection) throws IOException {
this.connection = connection;
int responseCode = connection.getResponseCode();
this.responseCode = responseCode == -1 ? 0 : responseCode;
responseMessage = connection.getResponseMessage();
List<String> headerNames = this.headerNames;
List<String> headerValues = this.headerValues;
for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
String key = entry.getKey();
if (key != null) {
for (String value : entry.getValue()) {
if (value != null) {
headerNames.add(key);
headerValues.add(value);
}
}
}
}
}
@Override
public int getStatusCode() {
return responseCode;
}
@Override
public InputStream getContent() throws IOException {
HttpURLConnection connection = this.connection;
return HttpStatusCodes.isSuccess(responseCode)
? connection.getInputStream() : connection.getErrorStream();
}
@Override
public String getContentEncoding() {
return connection.getContentEncoding();
}
@Override
public long getContentLength() {
String string = connection.getHeaderField("Content-Length");
return string == null ? -1 : Long.parseLong(string);
}
@Override
public String getContentType() {
return connection.getHeaderField("Content-Type");
}
@Override
public String getReasonPhrase() {
return responseMessage;
}
@Override
public String getStatusLine() {
String result = connection.getHeaderField(0);
return result != null && result.startsWith("HTTP/1.") ? result : null;
}
@Override
public int getHeaderCount() {
return headerNames.size();
}
@Override
public String getHeaderName(int index) {
return headerNames.get(index);
}
@Override
public String getHeaderValue(int index) {
return headerValues.get(index);
}
/**
* Closes the connection to the HTTP server.
*
* @since 1.4
*/
@Override
public void disconnect() {
connection.disconnect();
}
}
/*
* Copyright (c) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.api.client.http.okhttp;
import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.util.Beta;
import com.google.api.client.util.Preconditions;
import com.google.api.client.util.SecurityUtils;
import com.google.api.client.util.SslUtils;
import com.squareup.okhttp.OkHttpClient;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
/**
* Thread-safe HTTP low-level transport based on the {@code java.net} package.
* <p/>
* <p>
* Users should consider modifying the keep alive property on {@link OkHttpTransport} to control
* whether the socket should be returned to a pool of connected sockets. More information is
* available <a
* href='http://docs.oracle.com/javase/7/docs/technotes/guides/net/http-keepalive.html'>here</a>.
* </p>
* <p/>
* <p>
* We honor the default global caching behavior. To change the default behavior use
* {@link HttpURLConnection#setDefaultUseCaches(boolean)}.
* </p>
* <p/>
* <p>
* Upgrade warning: in prior version 1.14 caching was always disabled, but starting with version
* 1.15 we honor the default global caching behavior.
* </p>
* <p/>
* <p>
* Implementation is thread-safe. For maximum efficiency, applications should use a single
* globally-shared instance of the HTTP transport.
* </p>
*
* @author Yaniv Inbar
* @since 1.0
*/
public final class OkHttpTransport extends HttpTransport {
/**
* All valid request methods as specified in {@link HttpURLConnection#setRequestMethod}, sorted in
* ascending alphabetical order.
*/
private static final String[] SUPPORTED_METHODS = {HttpMethods.DELETE,
HttpMethods.GET,
HttpMethods.HEAD,
HttpMethods.OPTIONS,
HttpMethods.POST,
HttpMethods.PUT,
HttpMethods.TRACE};
static {
Arrays.sort(SUPPORTED_METHODS);
}
/**
* HTTP proxy or {@code null} to use the proxy settings from <a
* href="http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html">system
* properties</a>.
*/
private final Proxy proxy;
/**
* SSL socket factory or {@code null} for the default.
*/
private final SSLSocketFactory sslSocketFactory;
/**
* Host name verifier or {@code null} for the default.
*/
private final HostnameVerifier hostnameVerifier;
private final OkHttpClient okHttpClient;
/**
* Constructor with the default behavior.
* <p/>
* <p>
* Instead use {@link Builder} to modify behavior.
* </p>
*/
public OkHttpTransport() {
this(null, null, null);
}
/**
* @param proxy HTTP proxy or {@code null} to use the proxy settings from <a
* href="http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html">
* system properties</a>
* @param sslSocketFactory SSL socket factory or {@code null} for the default
* @param hostnameVerifier host name verifier or {@code null} for the default
*/
OkHttpTransport(
Proxy proxy, SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier) {
this.proxy = proxy;
this.sslSocketFactory = sslSocketFactory;
this.hostnameVerifier = hostnameVerifier;
this.okHttpClient = new OkHttpClient();
}
@Override
public boolean supportsMethod(String method) {
return Arrays.binarySearch(SUPPORTED_METHODS, method) >= 0;
}
@Override
protected OkHttpRequest buildRequest(String method, String url) throws IOException {
Preconditions.checkArgument(supportsMethod(method), "HTTP method %s not supported", method);
// connection with proxy settings
URL connUrl = new URL(url);
if (proxy != null) {
okHttpClient.setProxy(proxy);
}
HttpURLConnection connection = okHttpClient.open(connUrl);
connection.setRequestMethod(method);
// SSL settings
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection secureConnection = (HttpsURLConnection) connection;
if (hostnameVerifier != null) {
secureConnection.setHostnameVerifier(hostnameVerifier);
}
if (sslSocketFactory != null) {
secureConnection.setSSLSocketFactory(sslSocketFactory);
}
}
return new OkHttpRequest(connection);
}
/**
* Builder for {@link OkHttpTransport}.
* <p/>
* <p>
* Implementation is not thread-safe.
* </p>
*
* @since 1.13
*/
public static final class Builder {
/**
* SSL socket factory or {@code null} for the default.
*/
private SSLSocketFactory sslSocketFactory;
/**
* Host name verifier or {@code null} for the default.
*/
private HostnameVerifier hostnameVerifier;
/**
* HTTP proxy or {@code null} to use the proxy settings from <a
* href="http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html">system
* properties</a>.
*/
private Proxy proxy;
/**
* Sets the HTTP proxy or {@code null} to use the proxy settings from <a
* href="http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html">system
* properties</a>.
* <p/>
* <p>
* For example:
* </p>
* <p/>
* <pre>
* setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)))
* </pre>
*/
public Builder setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Sets the SSL socket factory based on root certificates in a Java KeyStore.
* <p/>
* <p>
* Example usage:
* </p>
* <p/>
* <pre>
* trustCertificatesFromJavaKeyStore(new FileInputStream("certs.jks"), "password");
* </pre>
*
* @param keyStoreStream input stream to the key store (closed at the end of this method in a
* finally block)
* @param storePass password protecting the key store file
* @since 1.14
*/
public Builder trustCertificatesFromJavaKeyStore(InputStream keyStoreStream, String storePass)
throws GeneralSecurityException, IOException {
KeyStore trustStore = SecurityUtils.getJavaKeyStore();
SecurityUtils.loadKeyStore(trustStore, keyStoreStream, storePass);
return trustCertificates(trustStore);
}
/**
* Sets the SSL socket factory based root certificates generated from the specified stream using
* {@link CertificateFactory#generateCertificates(InputStream)}.
* <p/>
* <p>
* Example usage:
* </p>
* <p/>
* <pre>
* trustCertificatesFromStream(new FileInputStream("certs.pem"));
* </pre>
*
* @param certificateStream certificate stream
* @since 1.14
*/
public Builder trustCertificatesFromStream(InputStream certificateStream)
throws GeneralSecurityException, IOException {
KeyStore trustStore = SecurityUtils.getJavaKeyStore();
trustStore.load(null, null);
SecurityUtils.loadKeyStoreFromCertificates(
trustStore, SecurityUtils.getX509CertificateFactory(), certificateStream);
return trustCertificates(trustStore);
}
/**
* Sets the SSL socket factory based on a root certificate trust store.
*
* @param trustStore certificate trust store (use for example {@link SecurityUtils#loadKeyStore}
* or {@link SecurityUtils#loadKeyStoreFromCertificates})
* @since 1.14
*/
public Builder trustCertificates(KeyStore trustStore) throws GeneralSecurityException {
SSLContext sslContext = SslUtils.getTlsSslContext();
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
return setSslSocketFactory(sslContext.getSocketFactory());
}
/**
* {@link Beta} <br/>
* Disables validating server SSL certificates by setting the SSL socket factory using
* {@link SslUtils#trustAllSSLContext()} for the SSL context and
* {@link SslUtils#trustAllHostnameVerifier()} for the host name verifier.
* <p/>
* <p>
* Be careful! Disabling certificate validation is dangerous and should only be done in testing
* environments.
* </p>
*/
@Beta
public Builder doNotValidateCertificate() throws GeneralSecurityException {
hostnameVerifier = SslUtils.trustAllHostnameVerifier();
sslSocketFactory = SslUtils.trustAllSSLContext().getSocketFactory();
return this;
}
/**
* Returns the SSL socket factory.
*/
public SSLSocketFactory getSslSocketFactory() {
return sslSocketFactory;
}
/**
* Sets the SSL socket factory or {@code null} for the default.
*/
public Builder setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
this.sslSocketFactory = sslSocketFactory;
return this;
}
/**
* Returns the host name verifier or {@code null} for the default.
*/
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
/**
* Sets the host name verifier or {@code null} for the default.
*/
public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
return this;
}
/**
* Returns a new instance of {@link OkHttpTransport} based on the options.
*/
public OkHttpTransport build() {
return new OkHttpTransport(proxy, sslSocketFactory, hostnameVerifier);
}
}
}
@danikula
Copy link

Thanks a lot! I'll try to use in my project instead of NetHttpTransport. I hope it'll fix annoying EOFException. See details here https://code.google.com/p/google-http-java-client/issues/detail?id=213

@kiwiandroiddev
Copy link

Thanks for this. What do you think about allowing an existing OkHttpClient to be passed in via OkHttpTransport's constructor? Also have you considered publishing this onto maven central?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment