Appearance
自定义 HTTP 代理(JSViewHttpProxy)
JSViewApp 的业务网络请求(本地化、升级下载、AppId 解析等)以及内核部分建连,都会经过统一出口 JSViewHttpRequester。
通过 JSViewHttpRequester.setHttpProxy(...) 注入自己的 JSViewHttpProxy,可以接管这些 HTTP 请求,例如:
- 使用 OkHttp / 自研网络库
- HttpDNS / 指定域名走固定 IP
- 统一加 Header、超时、证书策略等
未设置时,使用内置的 HttpURLConnection 实现。
调用链路
text
JsView 内核 / JSViewApp 业务
↓
JSViewHttpRequester
↓ openConnection(url)
JSViewHttpProxy(自定义)
↓ 返回 HttpConnection
真正发请求(OkHttp / HttpURLConnection / …)如何设置
在 Application.onCreate() 中尽早设置(建议在启动任何小程序之前):
java
import com.tvcode.js_view_app.util.http.JSViewHttpRequester;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 注入自定义 HTTP 代理
JSViewHttpRequester.setHttpProxy(new MyHttpProxy());
}
}设置后,以下调用都会走你的 Proxy:
JSViewHttpRequester.getText(...)JSViewHttpRequester.downloadFile(...)JSViewHttpRequester.getHttpConnection(...)- 内核通过
URLResolve发起的 HTTP(S) 连接
需要实现的接口
1. JSViewHttpProxy
java
package com.tvcode.js_view_app.util.http;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
public interface JSViewHttpProxy {
/**
* 创建一个连接
* @param url 连接地址
* @return 连接对象
*/
HttpConnection openConnection(URL url);
/**
* 可选:自定义 DNS 解析。默认使用系统 DNS。
*/
default List<InetAddress> lookup(String hostname) throws UnknownHostException {
// 默认实现:InetAddress.getAllByName(hostname)
...
}
}2. HttpConnection
把底层客户端(OkHttp / HttpURLConnection 等)包装成与 HttpURLConnection 语义接近的接口,需实现例如:
connect/disconnectgetResponseCode/getResponseMessagegetInputStream/getOutputStream/getErrorStreamsetRequestMethod/setRequestProperty/addRequestPropertysetConnectTimeout/setReadTimeout- HTTPS:
setSSLSocketFactory/setHostnameVerifier/ 证书相关方法
完整方法列表见 SDK 中的 com.tvcode.js_view_app.util.http.HttpConnection。
最小实现示例(OkHttp)
下面示例演示如何实现 JSViewHttpProxy,并用 OkHttp 实现完整的 HttpConnection。可按业务再增强(自定义 DNS、重试、缓存等)。
java
import com.tvcode.js_view_app.util.http.HttpConnection;
import com.tvcode.js_view_app.util.http.JSViewHttpProxy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ProtocolException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
public class MyHttpProxy implements JSViewHttpProxy {
private final OkHttpClient mHttpClient = new OkHttpClient();
@Override
public HttpConnection openConnection(URL url) {
return new MyHttpConnection(mHttpClient, url);
}
}
class MyHttpConnection implements HttpConnection {
private Response mResponse;
private final Request.Builder mRequestBuilder;
private RequestBody mRequestBody;
private final OkHttpClient.Builder mClientBuilder;
private OkHttpClient mHttpClient;
private String mMethod = "GET";
private final Map<String, List<String>> mRequestProperties = new HashMap<>();
MyHttpConnection(OkHttpClient httpClient, URL url) {
mRequestBuilder = new Request.Builder().url(url);
mClientBuilder = httpClient.newBuilder();
}
private void makeResponse() throws IOException {
if (mHttpClient == null) {
mHttpClient = mClientBuilder.build();
}
if (mResponse == null) {
for (Map.Entry<String, List<String>> entry : mRequestProperties.entrySet()) {
String key = entry.getKey();
for (String value : entry.getValue()) {
mRequestBuilder.addHeader(key, value);
}
}
mResponse = mHttpClient.newCall(mRequestBuilder.build()).execute();
}
}
@Override
public void connect() throws IOException {
if (mRequestBuilder == null) {
throw new IOException("connection is null");
}
}
@Override
public int getResponseCode() throws IOException {
makeResponse();
if (mResponse == null) {
throw new IOException("connection is null");
}
return mResponse.code();
}
@Override
public String getResponseMessage() throws IOException {
makeResponse();
if (mResponse == null) {
throw new IOException("connection is null");
}
return mResponse.message();
}
@Override
public long getContentLength() throws IOException {
makeResponse();
if (mResponse == null) {
throw new IOException("connection is null");
}
if (mResponse.body() == null) {
return 0;
}
return mResponse.body().contentLength();
}
@Override
public InputStream getInputStream() throws IOException {
makeResponse();
if (mResponse == null || mResponse.body() == null) {
throw new IOException("connection is null");
}
return mResponse.body().byteStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
if (mRequestBuilder == null) {
throw new IOException("connection is null");
}
if (mRequestBody == null) {
mRequestBody = new ByteArrayRequestBody();
mRequestBuilder.post(mRequestBody);
}
return ((ByteArrayRequestBody) mRequestBody).getOutputStream();
}
@Override
public void disconnect() {
if (mResponse != null) {
mResponse.close();
}
}
@Override
public boolean usingProxy() {
return false;
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
if ("GET".equals(method) || "HEAD".equals(method)) {
mRequestBody = null;
} else {
if (mRequestBody == null) {
mRequestBody = new ByteArrayRequestBody();
}
}
mRequestBuilder.method(method, mRequestBody);
mMethod = method;
}
@Override
public Map<String, List<String>> getRequestProperties() {
return mRequestProperties;
}
@Override
public String getRequestMethod() {
return mMethod;
}
@Override
public void setRequestProperty(String key, String value) {
List<String> values = new ArrayList<>();
values.add(value);
mRequestProperties.put(key, values);
}
@Override
public void addRequestProperty(String key, String value) {
if (!mRequestProperties.containsKey(key)) {
setRequestProperty(key, value);
} else {
mRequestProperties.get(key).add(value);
}
}
@Override
public String getRequestProperty(String key) {
List<String> values = mRequestProperties.get(key);
return (values != null && !values.isEmpty()) ? values.get(0) : null;
}
@Override
public void setConnectTimeout(int timeout) {
mClientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
}
@Override
public int getConnectTimeout() {
return mClientBuilder.build().connectTimeoutMillis();
}
@Override
public void setReadTimeout(int timeout) {
mClientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
}
@Override
public void setInstanceFollowRedirects(boolean followRedirects) {
mClientBuilder.followRedirects(followRedirects);
}
@Override
public boolean getInstanceFollowRedirects() {
return mClientBuilder.build().followRedirects();
}
@Override
public void setDoInput(boolean doinput) {
}
@Override
public boolean getDoInput() {
return true;
}
@Override
public InputStream getErrorStream() {
return null;
}
@Override
public String getHeaderField(String name) {
if (mResponse != null) {
return mResponse.header(name);
}
return null;
}
@Override
public Map<String, List<String>> getHeaderFields() {
if (mResponse == null) {
return null;
}
return mResponse.headers().toMultimap();
}
@Override
public void setUseCaches(boolean usecaches) {
}
@Override
public String getCipherSuite() {
return null;
}
@Override
public Certificate[] getLocalCertificates() {
return new Certificate[0];
}
@Override
public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
return new Certificate[0];
}
@Override
public void setSSLSocketFactory(SSLSocketFactory sf) {
try {
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException(
"Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
mClientBuilder.sslSocketFactory(sf, trustManager);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException(e);
}
}
@Override
public void setHostnameVerifier(HostnameVerifier v) {
mClientBuilder.hostnameVerifier(v);
}
}
class ByteArrayRequestBody extends RequestBody {
private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@Override
public MediaType contentType() {
return null;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(byteArrayOutputStream.toByteArray());
}
public OutputStream getOutputStream() {
return byteArrayOutputStream;
}
}自定义 DNS 时,覆写 lookup 即可,例如指定域名返回固定 IP:
java
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if ("cdn.example.com".equals(hostname)) {
return Collections.singletonList(InetAddress.getByName("1.2.3.4"));
}
return JSViewHttpProxy.super.lookup(hostname);
}注意事项
- 尽早设置:在
Application.onCreate中设置,避免部分请求已走默认实现。 - 返回
null:openConnection若返回null,JSViewHttpRequester会回退到默认HttpURLConnection。 - 仅 HTTP(S):内核
URLResolve对非http/https的 URL 不会走 Proxy。 - 线程安全:
openConnection可能在多线程调用,底层客户端建议使用线程安全实现(如共享OkHttpClient)。 - 按需增强:示例为可运行的简易实现;上线前可按业务补齐重试、缓存、错误流、证书策略等。