UrlConnectionTransport.java
// SPDX-License-Identifier: Apache-2.0
package com.shengzhiai.yugu.stcompat.internal;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** {@link HttpTransport} on {@link HttpURLConnection}; no third-party HTTP client. */
public final class UrlConnectionTransport implements HttpTransport {
/** Responses larger than this are truncated (results are a few dozen KB). */
static final int MAX_BODY = 8 * 1024 * 1024;
/**
* Stream the request body (fixed length) on Android. The desktop JDK implementation turns a
* 401 answer to a streamed body into an HttpRetryException and drops the error body, which
* would hide platform codes such as 2003; there the body is buffered instead (tests, JVM use).
*/
static final boolean STREAM_BODY = isAndroidRuntime();
/** java.vm.name is "Dalvik" on Dalvik and ART, java.vendor is "The Android Project". */
static boolean isAndroidRuntime() {
String vm = System.getProperty("java.vm.name", "").toLowerCase(java.util.Locale.ROOT);
String vendor = System.getProperty("java.vendor", "").toLowerCase(java.util.Locale.ROOT);
return vm.contains("dalvik") || vendor.contains("android");
}
@Override
public Response execute(Request request, CancelToken token) throws IOException {
final HttpURLConnection c = (HttpURLConnection) new URL(request.url).openConnection();
Closeable abort = new Closeable() {
@Override
public void close() {
c.disconnect();
}
};
if (token != null && !token.attach(abort)) {
throw new IOException("cancelled");
}
try {
c.setConnectTimeout(request.connectTimeoutMs);
c.setReadTimeout(request.readTimeoutMs);
c.setRequestMethod(request.method);
c.setUseCaches(false);
c.setInstanceFollowRedirects(false);
for (Map.Entry<String, String> h : request.headers.entrySet()) {
c.setRequestProperty(h.getKey(), h.getValue());
}
if (request.body != null) {
c.setDoOutput(true);
c.setRequestProperty("Content-Type", request.body.contentType());
if (STREAM_BODY) {
c.setFixedLengthStreamingMode(request.body.contentLength());
}
OutputStream out = c.getOutputStream();
try {
request.body.writeTo(out);
} finally {
out.close();
}
}
int status;
try {
status = c.getResponseCode();
} catch (HttpRetryException e) {
// authentication challenge on a streamed body: the status is all that is left
return new Response(e.responseCode(), new LinkedHashMap<String, String>(), "");
}
InputStream in = status >= 400 ? c.getErrorStream() : c.getInputStream();
String body = in == null ? "" : readAll(in);
Map<String, String> headers = new LinkedHashMap<String, String>();
for (Map.Entry<String, List<String>> e : c.getHeaderFields().entrySet()) {
if (e.getKey() != null && e.getValue() != null && !e.getValue().isEmpty()) {
headers.put(e.getKey(), e.getValue().get(0));
}
}
return new Response(status, headers, body);
} finally {
if (token != null) {
token.detach(abort);
}
c.disconnect();
}
}
static String readAll(InputStream in) throws IOException {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) > 0) {
if (bos.size() + n > MAX_BODY) {
bos.write(buf, 0, MAX_BODY - bos.size());
break;
}
bos.write(buf, 0, n);
}
return new String(bos.toByteArray(), Codec.UTF_8);
} finally {
in.close();
}
}
}