org.kohsuke.github.extras.okhttp3.ObsoleteUrlFactory Maven / Gradle / Ivy
package org.kohsuke.github.extras.okhttp3;
/*
* Copyright (C) 2014 Square, 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.
*/
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dispatcher;
import okhttp3.Handshake;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSink;
import okio.Okio;
import okio.Pipe;
import okio.Timeout;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.SocketPermission;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.Permission;
import java.security.Principal;
import java.security.cert.Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
/**
* OkHttp 3.14 dropped support for the long-deprecated OkUrlFactory class, which allows you to use the HttpURLConnection
* API with OkHttp's implementation. This class does the same thing using only public APIs in OkHttp. It requires OkHttp
* 3.14 or newer.
*
*
* Rather than pasting this 1100 line gist into your source code, please upgrade to OkHttp's request/response API. Your
* code will be shorter, easier to read, and you'll be able to use interceptors.
*/
@SuppressFBWarnings(value = { "EI_EXPOSE_REP", "EI_EXPOSE_REP2" }, justification = "Deprecated external code")
@Deprecated
public final class ObsoleteUrlFactory implements URLStreamHandlerFactory, Cloneable {
static final String SELECTED_PROTOCOL = "ObsoleteUrlFactory-Selected-Protocol";
static final String RESPONSE_SOURCE = "ObsoleteUrlFactory-Response-Source";
static final Set METHODS = new LinkedHashSet<>(
Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH"));
static final TimeZone UTC = TimeZone.getTimeZone("GMT");
static final int HTTP_CONTINUE = 100;
static final ThreadLocal STANDARD_DATE_FORMAT = ThreadLocal.withInitial(() -> {
// Date format specified by RFC 7231 section 7.1.1.1.
DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
rfc1123.setLenient(false);
rfc1123.setTimeZone(UTC);
return rfc1123;
});
static final Comparator FIELD_NAME_COMPARATOR = (a, b) -> {
if (Objects.equals(a, b)) {
return 0;
} else if (Objects.isNull(a)) {
return -1;
} else if (Objects.isNull(b)) {
return 1;
} else {
return String.CASE_INSENSITIVE_ORDER.compare(a, b);
}
};
private OkHttpClient client;
/**
* Instantiates a new Obsolete url factory.
*
* @param client
* the client
*/
public ObsoleteUrlFactory(OkHttpClient client) {
this.client = client;
}
/**
* Client ok http client.
*
* @return the ok http client
*/
public OkHttpClient client() {
return client;
}
/**
* Sets client.
*
* @param client
* the client
* @return the client
*/
public ObsoleteUrlFactory setClient(OkHttpClient client) {
this.client = client;
return this;
}
/**
* Returns a copy of this stream handler factory that includes a shallow copy of the internal
* {@linkplain OkHttpClient HTTP client}.
*/
@Override
public ObsoleteUrlFactory clone() {
return new ObsoleteUrlFactory(client);
}
/**
* Open http url connection.
*
* @param url
* the url
* @return the http url connection
*/
public HttpURLConnection open(URL url) {
return open(url, client.proxy());
}
HttpURLConnection open(URL url, @Nullable Proxy proxy) {
String protocol = url.getProtocol();
OkHttpClient copy = client.newBuilder().proxy(proxy).build();
if (protocol.equals("http"))
return new OkHttpURLConnection(url, copy);
if (protocol.equals("https"))
return new OkHttpsURLConnection(url, copy);
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
/**
* Creates a URLStreamHandler as a {@link java.net.URL#setURLStreamHandlerFactory}.
*
*
* This code configures OkHttp to handle all HTTP and HTTPS connections created with
* {@link java.net.URL#openConnection()}:
*
*
* {
* @code
*
* OkHttpClient okHttpClient = new OkHttpClient();
* URL.setURLStreamHandlerFactory(new ObsoleteUrlFactory(okHttpClient));
* }
*
*/
@Override
public URLStreamHandler createURLStreamHandler(final String protocol) {
if (!protocol.equals("http") && !protocol.equals("https"))
return null;
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) {
return open(url);
}
@Override
protected URLConnection openConnection(URL url, Proxy proxy) {
return open(url, proxy);
}
@Override
protected int getDefaultPort() {
if (protocol.equals("http"))
return 80;
if (protocol.equals("https"))
return 443;
throw new AssertionError();
}
};
}
static String format(Date value) {
return STANDARD_DATE_FORMAT.get().format(value);
}
static boolean permitsRequestBody(String method) {
return !(method.equals("GET") || method.equals("HEAD"));
}
/** Returns true if the response must have a (possibly 0-length) body. See RFC 7231. */
static boolean hasBody(Response response) {
// HEAD requests never yield a body regardless of the response headers.
if (response.request().method().equals("HEAD")) {
return false;
}
int responseCode = response.code();
if ((responseCode < HTTP_CONTINUE || responseCode >= 200) && responseCode != HTTP_NO_CONTENT
&& responseCode != HTTP_NOT_MODIFIED) {
return true;
}
// If the Content-Length or Transfer-Encoding headers disagree with the response code, the
// response is malformed. For best compatibility, we honor the headers.
if (contentLength(response.headers()) != -1
|| "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return true;
}
return false;
}
static long contentLength(Headers headers) {
String s = headers.get("Content-Length");
if (s == null)
return -1;
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
return -1;
}
}
static String responseSourceHeader(Response response) {
Response networkResponse = response.networkResponse();
if (networkResponse == null) {
return response.cacheResponse() == null ? "NONE" : "CACHE " + response.code();
} else {
return response.cacheResponse() == null
? "NETWORK " + response.code()
: "CONDITIONAL_CACHE " + networkResponse.code();
}
}
static String statusLineToString(Response response) {
return (response.protocol() == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1") + ' ' + response.code() + ' '
+ response.message();
}
static String toHumanReadableAscii(String s) {
for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) {
c = s.codePointAt(i);
if (c > '\u001f' && c < '\u007f')
continue;
try (Buffer buffer = new Buffer()) {
buffer.writeUtf8(s, 0, i);
buffer.writeUtf8CodePoint('?');
for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {
c = s.codePointAt(j);
buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
}
return buffer.readUtf8();
}
}
return s;
}
static Map> toMultimap(Headers headers, @Nullable String valueForNullKey) {
Map> result = new TreeMap<>(FIELD_NAME_COMPARATOR);
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
List allValues = new ArrayList<>();
List otherValues = result.get(fieldName);
if (otherValues != null) {
allValues.addAll(otherValues);
}
allValues.add(value);
result.put(fieldName, Collections.unmodifiableList(allValues));
}
if (valueForNullKey != null) {
result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));
}
return Collections.unmodifiableMap(result);
}
static String getSystemProperty(String key, @Nullable String defaultValue) {
String value;
try {
value = System.getProperty(key);
} catch (SecurityException | IllegalArgumentException ex) {
return defaultValue;
}
return value != null ? value : defaultValue;
}
static String defaultUserAgent() {
String agent = getSystemProperty("http.agent", null);
return agent != null ? toHumanReadableAscii(agent) : "ObsoleteUrlFactory";
}
static IOException propagate(Throwable throwable) throws IOException {
if (throwable instanceof IOException)
throw (IOException) throwable;
if (throwable instanceof Error)
throw (Error) throwable;
if (throwable instanceof RuntimeException)
throw (RuntimeException) throwable;
throw new AssertionError();
}
static final class OkHttpURLConnection extends HttpURLConnection implements Callback {
// These fields are confined to the application thread that uses HttpURLConnection.
OkHttpClient client;
final NetworkInterceptor networkInterceptor = new NetworkInterceptor();
Headers.Builder requestHeaders = new Headers.Builder();
Headers responseHeaders;
boolean executed;
Call call;
/** Like the superclass field of the same name, but a long and available on all platforms. */
long fixedContentLength = -1L;
// These fields are guarded by lock.
private final Object lock = new Object();
private Response response;
private Throwable callFailure;
Response networkResponse;
boolean connectPending = true;
Proxy proxy;
Handshake handshake;
OkHttpURLConnection(URL url, OkHttpClient client) {
super(url);
this.client = client;
}
@Override
public void connect() throws IOException {
if (executed)
return;
Call call = buildCall();
executed = true;
call.enqueue(this);
synchronized (lock) {
try {
while (connectPending && response == null && callFailure == null) {
lock.wait(); // Wait 'til the network interceptor is reached or the call fails.
}
if (callFailure != null) {
throw propagate(callFailure);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Retain interrupted status.
throw new InterruptedIOException();
}
}
}
@Override
public void disconnect() {
// Calling disconnect() before a connection exists should have no effect.
if (call == null)
return;
networkInterceptor.proceed(); // Unblock any waiting async thread.
call.cancel();
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "hasBody checks for this")
@Override
public InputStream getErrorStream() {
try {
Response response = getResponse(true);
if (hasBody(response) && response.code() >= HTTP_BAD_REQUEST) {
return new ResponseBodyInputStream(response.body());
}
return null;
} catch (IOException e) {
return null;
}
}
Headers getHeaders() throws IOException {
if (responseHeaders == null) {
Response response = getResponse(true);
Headers headers = response.headers();
responseHeaders = headers.newBuilder()
.add(SELECTED_PROTOCOL, response.protocol().toString())
.add(RESPONSE_SOURCE, responseSourceHeader(response))
.build();
}
return responseHeaders;
}
@Override
public String getHeaderField(int position) {
try {
Headers headers = getHeaders();
if (position < 0 || position >= headers.size())
return null;
return headers.value(position);
} catch (IOException e) {
return null;
}
}
@Override
public String getHeaderField(String fieldName) {
try {
return fieldName == null ? statusLineToString(getResponse(true)) : getHeaders().get(fieldName);
} catch (IOException e) {
return null;
}
}
@Override
public String getHeaderFieldKey(int position) {
try {
Headers headers = getHeaders();
if (position < 0 || position >= headers.size())
return null;
return headers.name(position);
} catch (IOException e) {
return null;
}
}
@Override
public Map> getHeaderFields() {
try {
return toMultimap(getHeaders(), statusLineToString(getResponse(true)));
} catch (IOException e) {
return Collections.emptyMap();
}
}
@Override
public Map> getRequestProperties() {
if (connected) {
throw new IllegalStateException("Cannot access request header fields after connection is set");
}
return toMultimap(requestHeaders.build(), null);
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
justification = "Good request will have body")
@Override
public InputStream getInputStream() throws IOException {
if (!doInput) {
throw new ProtocolException("This protocol does not support input");
}
Response response = getResponse(false);
if (response.code() >= HTTP_BAD_REQUEST)
throw new FileNotFoundException(url.toString());
return new ResponseBodyInputStream(response.body());
}
@Override
public OutputStream getOutputStream() throws IOException {
OutputStreamRequestBody requestBody = (OutputStreamRequestBody) buildCall().request().body();
if (requestBody == null) {
throw new ProtocolException("method does not support a request body: " + method);
}
if (requestBody instanceof StreamedRequestBody) {
connect();
networkInterceptor.proceed();
}
if (requestBody.closed) {
throw new ProtocolException("cannot write request body after response has been read");
}
return requestBody.outputStream;
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
justification = "usingProxy() handles this")
@Override
public Permission getPermission() {
URL url = getURL();
String hostname = url.getHost();
int hostPort = url.getPort() != -1 ? url.getPort() : HttpUrl.defaultPort(url.getProtocol());
if (usingProxy()) {
InetSocketAddress proxyAddress = (InetSocketAddress) client.proxy().address();
hostname = proxyAddress.getHostName();
hostPort = proxyAddress.getPort();
}
return new SocketPermission(hostname + ":" + hostPort, "connect, resolve");
}
@Override
public String getRequestProperty(String field) {
if (field == null)
return null;
return requestHeaders.get(field);
}
@Override
public void setConnectTimeout(int timeoutMillis) {
client = client.newBuilder().connectTimeout(timeoutMillis, TimeUnit.MILLISECONDS).build();
}
@Override
public void setInstanceFollowRedirects(boolean followRedirects) {
client = client.newBuilder().followRedirects(followRedirects).build();
}
@Override
public boolean getInstanceFollowRedirects() {
return client.followRedirects();
}
@Override
public int getConnectTimeout() {
return client.connectTimeoutMillis();
}
@Override
public void setReadTimeout(int timeoutMillis) {
client = client.newBuilder().readTimeout(timeoutMillis, TimeUnit.MILLISECONDS).build();
}
@Override
public int getReadTimeout() {
return client.readTimeoutMillis();
}
@SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
private Call buildCall() throws IOException {
if (call != null) {
return call;
}
connected = true;
if (doOutput) {
if (method.equals("GET")) {
method = "POST";
} else if (!permitsRequestBody(method)) {
throw new ProtocolException(method + " does not support writing");
}
}
if (requestHeaders.get("User-Agent") == null) {
requestHeaders.add("User-Agent", defaultUserAgent());
}
OutputStreamRequestBody requestBody = null;
if (permitsRequestBody(method)) {
String contentType = requestHeaders.get("Content-Type");
if (contentType == null) {
contentType = "application/x-www-form-urlencoded";
requestHeaders.add("Content-Type", contentType);
}
boolean stream = fixedContentLength != -1L || chunkLength > 0;
long contentLength = -1L;
String contentLengthString = requestHeaders.get("Content-Length");
if (fixedContentLength != -1L) {
contentLength = fixedContentLength;
} else if (contentLengthString != null) {
contentLength = Long.parseLong(contentLengthString);
}
requestBody = stream ? new StreamedRequestBody(contentLength) : new BufferedRequestBody(contentLength);
requestBody.timeout.timeout(client.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
}
HttpUrl url;
try {
url = HttpUrl.get(getURL().toString());
} catch (IllegalArgumentException e) {
MalformedURLException malformedUrl = new MalformedURLException();
malformedUrl.initCause(e);
throw malformedUrl;
}
Request request = new Request.Builder().url(url)
.headers(requestHeaders.build())
.method(method, requestBody)
.build();
OkHttpClient.Builder clientBuilder = client.newBuilder();
clientBuilder.interceptors().clear();
clientBuilder.interceptors().add(UnexpectedException.INTERCEPTOR);
clientBuilder.networkInterceptors().clear();
clientBuilder.networkInterceptors().add(networkInterceptor);
// Use a separate dispatcher so that limits aren't impacted. But use the same executor service!
clientBuilder.dispatcher(new Dispatcher(client.dispatcher().executorService()));
// If we're currently not using caches, make sure the engine's client doesn't have one.
if (!getUseCaches()) {
clientBuilder.cache(null);
}
return call = clientBuilder.build().newCall(request);
}
private Response getResponse(boolean networkResponseOnError) throws IOException {
synchronized (lock) {
if (response != null)
return response;
if (callFailure != null) {
if (networkResponseOnError && networkResponse != null)
return networkResponse;
throw propagate(callFailure);
}
}
Call call = buildCall();
networkInterceptor.proceed();
OutputStreamRequestBody requestBody = (OutputStreamRequestBody) call.request().body();
if (requestBody != null)
requestBody.outputStream.close();
if (executed) {
synchronized (lock) {
try {
while (response == null && callFailure == null) {
lock.wait(); // Wait until the response is returned or the call fails.
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Retain interrupted status.
throw new InterruptedIOException();
}
}
} else {
executed = true;
try {
onResponse(call, call.execute());
} catch (IOException e) {
onFailure(call, e);
}
}
synchronized (lock) {
if (callFailure != null)
throw propagate(callFailure);
if (response != null)
return response;
}
throw new AssertionError();
}
@Override
public boolean usingProxy() {
if (proxy != null)
return true;
Proxy clientProxy = client.proxy();
return clientProxy != null && clientProxy.type() != Proxy.Type.DIRECT;
}
@Override
public String getResponseMessage() throws IOException {
return getResponse(true).message();
}
@Override
public int getResponseCode() throws IOException {
return getResponse(true).code();
}
@Override
public void setRequestProperty(String field, String newValue) {
if (connected) {
throw new IllegalStateException("Cannot set request property after connection is made");
}
if (field == null) {
throw new NullPointerException("field == null");
}
if (newValue == null) {
return;
}
requestHeaders.set(field, newValue);
}
@Override
public void setIfModifiedSince(long newValue) {
super.setIfModifiedSince(newValue);
if (ifModifiedSince != 0) {
requestHeaders.set("If-Modified-Since", format(new Date(ifModifiedSince)));
} else {
requestHeaders.removeAll("If-Modified-Since");
}
}
@Override
public void addRequestProperty(String field, String value) {
if (connected) {
throw new IllegalStateException("Cannot add request property after connection is made");
}
if (field == null) {
throw new NullPointerException("field == null");
}
if (value == null) {
return;
}
requestHeaders.add(field, value);
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
if (!METHODS.contains(method)) {
throw new ProtocolException("Expected one of " + METHODS + " but was " + method);
}
this.method = method;
}
@Override
public void setFixedLengthStreamingMode(int contentLength) {
setFixedLengthStreamingMode((long) contentLength);
}
@Override
public void setFixedLengthStreamingMode(long contentLength) {
if (super.connected)
throw new IllegalStateException("Already connected");
if (chunkLength > 0)
throw new IllegalStateException("Already in chunked mode");
if (contentLength < 0)
throw new IllegalArgumentException("contentLength < 0");
this.fixedContentLength = contentLength;
super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VALUE);
}
@Override
public void onFailure(Call call, IOException e) {
synchronized (lock) {
this.callFailure = (e instanceof UnexpectedException) ? e.getCause() : e;
lock.notifyAll();
}
}
@Override
public void onResponse(Call call, Response response) {
synchronized (lock) {
this.response = response;
this.handshake = response.handshake();
this.url = response.request().url().url();
lock.notifyAll();
}
}
final class NetworkInterceptor implements Interceptor {
// Guarded by HttpUrlConnection.this.
private boolean proceed;
/**
* Proceed.
*/
public void proceed() {
synchronized (lock) {
this.proceed = true;
lock.notifyAll();
}
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
justification = "If we get here there is a connection and request.body() is checked")
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
synchronized (lock) {
connectPending = false;
proxy = chain.connection().route().proxy();
handshake = chain.connection().handshake();
lock.notifyAll();
try {
while (!proceed) {
lock.wait(); // Wait until proceed() is called.
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Retain interrupted status.
throw new InterruptedIOException();
}
}
// Try to lock in the Content-Length before transmitting the request body.
if (request.body() instanceof OutputStreamRequestBody) {
OutputStreamRequestBody requestBody = (OutputStreamRequestBody) request.body();
request = requestBody.prepareToSendRequest(request);
}
Response response = chain.proceed(request);
synchronized (lock) {
networkResponse = response;
url = response.request().url().url();
}
return response;
}
}
}
abstract static class OutputStreamRequestBody extends RequestBody {
Timeout timeout;
long expectedContentLength;
OutputStream outputStream;
boolean closed;
void initOutputStream(BufferedSink sink, long expectedContentLength) {
this.timeout = sink.timeout();
this.expectedContentLength = expectedContentLength;
// An output stream that writes to sink. If expectedContentLength is not -1, then this expects
// exactly that many bytes to be written.
this.outputStream = new OutputStream() {
private long bytesReceived;
@Override
public void write(int b) throws IOException {
write(new byte[]{ (byte) b }, 0, 1);
}
@Override
public void write(byte[] source, int offset, int byteCount) throws IOException {
if (closed)
throw new IOException("closed"); // Not IllegalStateException!
if (expectedContentLength != -1L && bytesReceived + byteCount > expectedContentLength) {
throw new ProtocolException("expected " + expectedContentLength + " bytes but received "
+ bytesReceived + byteCount);
}
bytesReceived += byteCount;
try {
sink.write(source, offset, byteCount);
} catch (InterruptedIOException e) {
throw new SocketTimeoutException(e.getMessage());
}
}
@Override
public void flush() throws IOException {
if (closed)
return; // Weird, but consistent with historical behavior.
sink.flush();
}
@Override
public void close() throws IOException {
closed = true;
if (expectedContentLength != -1L && bytesReceived < expectedContentLength) {
throw new ProtocolException(
"expected " + expectedContentLength + " bytes but received " + bytesReceived);
}
sink.close();
}
};
}
@Override
public long contentLength() {
return expectedContentLength;
}
@Override
public final @Nullable MediaType contentType() {
return null; // Let the caller provide this in a regular header.
}
/**
* Prepare to send request request.
*
* @param request
* the request
* @return the request
* @throws IOException
* the io exception
*/
public Request prepareToSendRequest(Request request) throws IOException {
return request;
}
}
static final class BufferedRequestBody extends OutputStreamRequestBody {
final Buffer buffer = new Buffer();
long contentLength = -1L;
BufferedRequestBody(long expectedContentLength) {
initOutputStream(buffer, expectedContentLength);
}
@Override
public long contentLength() {
return contentLength;
}
@Override
public Request prepareToSendRequest(Request request) throws IOException {
if (request.header("Content-Length") != null)
return request;
outputStream.close();
contentLength = buffer.size();
return request.newBuilder()
.removeHeader("Transfer-Encoding")
.header("Content-Length", Long.toString(buffer.size()))
.build();
}
@Override
public void writeTo(BufferedSink sink) {
buffer.copyTo(sink.buffer(), 0, buffer.size());
}
}
static final class StreamedRequestBody extends OutputStreamRequestBody {
private final Pipe pipe = new Pipe(8192);
StreamedRequestBody(long expectedContentLength) {
initOutputStream(Okio.buffer(pipe.sink()), expectedContentLength);
}
@Override
public boolean isOneShot() {
return true;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
Buffer buffer = new Buffer();
while (pipe.source().read(buffer, 8192) != -1L) {
sink.write(buffer, buffer.size());
}
}
}
abstract static class DelegatingHttpsURLConnection extends HttpsURLConnection {
private final HttpURLConnection delegate;
DelegatingHttpsURLConnection(HttpURLConnection delegate) {
super(delegate.getURL());
this.delegate = delegate;
}
/**
* Handshake handshake.
*
* @return the handshake
*/
protected abstract Handshake handshake();
@Override
public abstract void setHostnameVerifier(HostnameVerifier hostnameVerifier);
@Override
public abstract HostnameVerifier getHostnameVerifier();
@Override
public abstract void setSSLSocketFactory(SSLSocketFactory sslSocketFactory);
@Override
public abstract SSLSocketFactory getSSLSocketFactory();
@Override
public String getCipherSuite() {
Handshake handshake = handshake();
return handshake != null ? handshake.cipherSuite().javaName() : null;
}
@Override
public Certificate[] getLocalCertificates() {
Handshake handshake = handshake();
if (handshake == null)
return null;
List result = handshake.localCertificates();
return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
}
@Override
public Certificate[] getServerCertificates() {
Handshake handshake = handshake();
if (handshake == null)
return null;
List result = handshake.peerCertificates();
return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
}
@Override
public Principal getPeerPrincipal() {
Handshake handshake = handshake();
return handshake != null ? handshake.peerPrincipal() : null;
}
@Override
public Principal getLocalPrincipal() {
Handshake handshake = handshake();
return handshake != null ? handshake.localPrincipal() : null;
}
@Override
public void connect() throws IOException {
connected = true;
delegate.connect();
}
@Override
public void disconnect() {
delegate.disconnect();
}
@Override
public InputStream getErrorStream() {
return delegate.getErrorStream();
}
@Override
public String getRequestMethod() {
return delegate.getRequestMethod();
}
@Override
public int getResponseCode() throws IOException {
return delegate.getResponseCode();
}
@Override
public String getResponseMessage() throws IOException {
return delegate.getResponseMessage();
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
delegate.setRequestMethod(method);
}
@Override
public boolean usingProxy() {
return delegate.usingProxy();
}
@Override
public boolean getInstanceFollowRedirects() {
return delegate.getInstanceFollowRedirects();
}
@Override
public void setInstanceFollowRedirects(boolean followRedirects) {
delegate.setInstanceFollowRedirects(followRedirects);
}
@Override
public boolean getAllowUserInteraction() {
return delegate.getAllowUserInteraction();
}
@Override
public Object getContent() throws IOException {
return delegate.getContent();
}
@Override
public Object getContent(Class[] types) throws IOException {
return delegate.getContent(types);
}
@Override
public String getContentEncoding() {
return delegate.getContentEncoding();
}
@Override
public int getContentLength() {
return delegate.getContentLength();
}
// Should only be invoked on Java 8+ or Android API 24+.
@Override
public long getContentLengthLong() {
return delegate.getContentLengthLong();
}
@Override
public String getContentType() {
return delegate.getContentType();
}
@Override
public long getDate() {
return delegate.getDate();
}
@Override
public boolean getDefaultUseCaches() {
return delegate.getDefaultUseCaches();
}
@Override
public boolean getDoInput() {
return delegate.getDoInput();
}
@Override
public boolean getDoOutput() {
return delegate.getDoOutput();
}
@Override
public long getExpiration() {
return delegate.getExpiration();
}
@Override
public String getHeaderField(int pos) {
return delegate.getHeaderField(pos);
}
@Override
public Map> getHeaderFields() {
return delegate.getHeaderFields();
}
@Override
public Map> getRequestProperties() {
return delegate.getRequestProperties();
}
@Override
public void addRequestProperty(String field, String newValue) {
delegate.addRequestProperty(field, newValue);
}
@Override
public String getHeaderField(String key) {
return delegate.getHeaderField(key);
}
// Should only be invoked on Java 8+ or Android API 24+.
@Override
public long getHeaderFieldLong(String field, long defaultValue) {
return delegate.getHeaderFieldLong(field, defaultValue);
}
@Override
public long getHeaderFieldDate(String field, long defaultValue) {
return delegate.getHeaderFieldDate(field, defaultValue);
}
@Override
public int getHeaderFieldInt(String field, int defaultValue) {
return delegate.getHeaderFieldInt(field, defaultValue);
}
@Override
public String getHeaderFieldKey(int position) {
return delegate.getHeaderFieldKey(position);
}
@Override
public long getIfModifiedSince() {
return delegate.getIfModifiedSince();
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public long getLastModified() {
return delegate.getLastModified();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public Permission getPermission() throws IOException {
return delegate.getPermission();
}
@Override
public String getRequestProperty(String field) {
return delegate.getRequestProperty(field);
}
@Override
public URL getURL() {
return delegate.getURL();
}
@Override
public boolean getUseCaches() {
return delegate.getUseCaches();
}
@Override
public void setAllowUserInteraction(boolean newValue) {
delegate.setAllowUserInteraction(newValue);
}
@Override
public void setDefaultUseCaches(boolean newValue) {
delegate.setDefaultUseCaches(newValue);
}
@Override
public void setDoInput(boolean newValue) {
delegate.setDoInput(newValue);
}
@Override
public void setDoOutput(boolean newValue) {
delegate.setDoOutput(newValue);
}
// Should only be invoked on Java 8+ or Android API 24+.
@Override
public void setFixedLengthStreamingMode(long contentLength) {
delegate.setFixedLengthStreamingMode(contentLength);
}
@Override
public void setIfModifiedSince(long newValue) {
delegate.setIfModifiedSince(newValue);
}
@Override
public void setRequestProperty(String field, String newValue) {
delegate.setRequestProperty(field, newValue);
}
@Override
public void setUseCaches(boolean newValue) {
delegate.setUseCaches(newValue);
}
@Override
public void setConnectTimeout(int timeoutMillis) {
delegate.setConnectTimeout(timeoutMillis);
}
@Override
public int getConnectTimeout() {
return delegate.getConnectTimeout();
}
@Override
public void setReadTimeout(int timeoutMillis) {
delegate.setReadTimeout(timeoutMillis);
}
@Override
public int getReadTimeout() {
return delegate.getReadTimeout();
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public void setFixedLengthStreamingMode(int contentLength) {
delegate.setFixedLengthStreamingMode(contentLength);
}
@Override
public void setChunkedStreamingMode(int chunkLength) {
delegate.setChunkedStreamingMode(chunkLength);
}
}
static final class OkHttpsURLConnection extends DelegatingHttpsURLConnection {
private final OkHttpURLConnection delegate;
OkHttpsURLConnection(URL url, OkHttpClient client) {
this(new OkHttpURLConnection(url, client));
}
OkHttpsURLConnection(OkHttpURLConnection delegate) {
super(delegate);
this.delegate = delegate;
}
@Override
protected Handshake handshake() {
if (delegate.call == null) {
throw new IllegalStateException("Connection has not yet been established");
}
return delegate.handshake;
}
@Override
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
delegate.client = delegate.client.newBuilder().hostnameVerifier(hostnameVerifier).build();
}
@Override
public HostnameVerifier getHostnameVerifier() {
return delegate.client.hostnameVerifier();
}
@Override
public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
if (sslSocketFactory == null) {
throw new IllegalArgumentException("sslSocketFactory == null");
}
// This fails in JDK 9 because OkHttp is unable to extract the trust manager.
delegate.client = delegate.client.newBuilder().sslSocketFactory(sslSocketFactory).build();
}
@Override
public SSLSocketFactory getSSLSocketFactory() {
return delegate.client.sslSocketFactory();
}
}
static final class UnexpectedException extends IOException {
static final Interceptor INTERCEPTOR = chain -> {
try {
return chain.proceed(chain.request());
} catch (Error | RuntimeException e) {
throw new UnexpectedException(e);
}
};
UnexpectedException(Throwable cause) {
super(cause);
}
}
/**
* Make sure both the ResponseBody and the InputStream are closed when the InputStream coming from the ResponseBody
* is closed.
*/
private static final class ResponseBodyInputStream extends InputStream {
private final ResponseBody responseBody;
private final InputStream inputStream;
private ResponseBodyInputStream(ResponseBody responseBody) {
this.responseBody = responseBody;
this.inputStream = responseBody.byteStream();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte b[]) throws IOException {
return inputStream.read(b);
}
@Override
public int read(byte b[], int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return inputStream.skip(n);
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public synchronized void mark(int readlimit) {
inputStream.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
inputStream.reset();
}
@Override
public boolean markSupported() {
return inputStream.markSupported();
}
@Override
public void close() throws IOException {
try {
inputStream.close();
} finally {
responseBody.close();
}
}
}
}