|
|
@ -21,30 +21,89 @@ import java.util.concurrent.TimeUnit; |
|
|
|
* <p>使用Okhttp的Requester,简单的实现了mywust的需求,能应付普通的使用</p> |
|
|
|
* <p>使用Okhttp的Requester,简单的实现了mywust的需求,能应付普通的使用</p> |
|
|
|
* <p>可以设置OkhttpClient是否使用单例模式以节省部分内存和对象分配的消耗,默认不使用(多例模式)</p> |
|
|
|
* <p>可以设置OkhttpClient是否使用单例模式以节省部分内存和对象分配的消耗,默认不使用(多例模式)</p> |
|
|
|
* <p>按照Okhttp官方的说法以及其他人的测试,使用多例Okhttp对性能消耗其实并不是很大,可以忽略,在多例模式下性能可能也比httpclient要好</p> |
|
|
|
* <p>按照Okhttp官方的说法以及其他人的测试,使用多例Okhttp对性能消耗其实并不是很大,可以忽略,在多例模式下性能可能也比httpclient要好</p> |
|
|
|
* <p>但是如果每次请求的代理或者超时等设置有动态需求的话只能使用多例模式,单例模式下OkhttpClient的设置是全局一致的</p> |
|
|
|
* <p>但是如果每次请求的代理或者超时等设置有动态需求的话<b>只能使用多例模式</b>,因为单例模式下OkhttpClient的设置是全局一致的,无法定制选项</p> |
|
|
|
|
|
|
|
* <p>详见:<a href="https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/">官方OkHttpClient文档</a></p> |
|
|
|
|
|
|
|
* <p>StackOverflow上也有人讨论过:<a href="https://stackoverflow.com/questions/42955571/how-to-set-proxy-for-each-request-in-okhttp">How to set proxy for each request in OkHttp?</a></p> |
|
|
|
|
|
|
|
* <p>注意,这里的“多例”并不是每次都new一个builder来build,这种情况每个client都有一个独立的连接池和线程,多了会导致oom,因此如果想要“多例”,应该是rootClient.newBuild()而不是new Builder().build()</p> |
|
|
|
|
|
|
|
* <p>总之看文档就知道了</p> |
|
|
|
* <br> |
|
|
|
* <br> |
|
|
|
* <p>如果有更加高级的需求实现,也可以自己实现Requester接口</p> |
|
|
|
* <p>如果有更加高级的需求实现,也可以自己实现Requester接口</p> |
|
|
|
* |
|
|
|
* |
|
|
|
* @author lensfrex |
|
|
|
* @author lensfrex |
|
|
|
* @create 2022-10-15 09:49 |
|
|
|
* @create 2022-10-15 09:49 |
|
|
|
* @edit 2022-10-15 09:49 |
|
|
|
* @edit 2022-10-19 21:30 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class SimpleOkhttpRequester implements Requester { |
|
|
|
public class SimpleOkhttpRequester implements Requester { |
|
|
|
private final boolean singletonClient; |
|
|
|
private final boolean useSingletonClient; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static volatile OkHttpClient rootClient; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 默认的,多例模式的SimpleOkhttpRequester构造方法 |
|
|
|
|
|
|
|
*/ |
|
|
|
public SimpleOkhttpRequester() { |
|
|
|
public SimpleOkhttpRequester() { |
|
|
|
singletonClient = false; |
|
|
|
this(new OkHttpClient(), false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public SimpleOkhttpRequester(boolean singletonClient) { |
|
|
|
/** |
|
|
|
this.singletonClient = singletonClient; |
|
|
|
* 指定是否使用单例模式的SimpleOkhttpRequester构造方法 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param useSingletonClient 是否使用单例模式 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public SimpleOkhttpRequester(boolean useSingletonClient) { |
|
|
|
|
|
|
|
this(new OkHttpClient(), useSingletonClient); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static volatile OkHttpClient singletonOkhttp; |
|
|
|
/** |
|
|
|
|
|
|
|
* 使用特定okhttpClient对象并且指定是否使用单例模式的SimpleOkhttpRequester私有构造方法 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param okHttpClient okhttpClient对象 |
|
|
|
|
|
|
|
* @param useSingletonClient 是否使用单例模式 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private SimpleOkhttpRequester(OkHttpClient okHttpClient, boolean useSingletonClient) { |
|
|
|
|
|
|
|
this.useSingletonClient = useSingletonClient; |
|
|
|
|
|
|
|
this.setRootClient(okHttpClient); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static OkHttpClient newOkhttpClient(RequestClientOption requestClientOption) { |
|
|
|
/** |
|
|
|
OkHttpClient.Builder builder = new OkHttpClient.Builder() |
|
|
|
* 指定client参数选项的SimpleOkhttpRequester构造方法,默认不使用单例模式 |
|
|
|
.callTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS) |
|
|
|
* |
|
|
|
|
|
|
|
* @param requestClientOption client参数选项 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public SimpleOkhttpRequester(RequestClientOption requestClientOption) { |
|
|
|
|
|
|
|
this(requestClientOption, false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 指定client参数选项的SimpleOkhttpRequester构造方法,并且指定是否使用单例模式 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param requestClientOption client参数选项 |
|
|
|
|
|
|
|
* @param useSingletonClient 是否使用单例模式 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public SimpleOkhttpRequester(RequestClientOption requestClientOption, boolean useSingletonClient) { |
|
|
|
|
|
|
|
this.useSingletonClient = useSingletonClient; |
|
|
|
|
|
|
|
this.setRootClient(buildClient(new OkHttpClient.Builder(), requestClientOption)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void setRootClient(OkHttpClient okHttpClient) { |
|
|
|
|
|
|
|
if (rootClient == null) { |
|
|
|
|
|
|
|
synchronized (SimpleOkhttpRequester.class) { |
|
|
|
|
|
|
|
if (rootClient == null) { |
|
|
|
|
|
|
|
rootClient = okHttpClient; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 构建一个okhttpClient |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param builder okhttpClient.Builder |
|
|
|
|
|
|
|
* @param requestClientOption client参数选项 |
|
|
|
|
|
|
|
* @return 构建好的client |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private OkHttpClient buildClient(OkHttpClient.Builder builder, RequestClientOption requestClientOption) { |
|
|
|
|
|
|
|
builder.callTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS) |
|
|
|
.readTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS) |
|
|
|
.readTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS) |
|
|
|
.connectTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS); |
|
|
|
.connectTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS); |
|
|
|
|
|
|
|
|
|
|
@ -60,34 +119,63 @@ public class SimpleOkhttpRequester implements Requester { |
|
|
|
return builder.build(); |
|
|
|
return builder.build(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static OkHttpClient getSingletonClientInstant(RequestClientOption options) { |
|
|
|
/** |
|
|
|
if (singletonOkhttp == null) { |
|
|
|
* 获取client,若为单例模式,直接返回rootClient |
|
|
|
synchronized (SimpleOkhttpRequester.class) { |
|
|
|
* |
|
|
|
if (singletonOkhttp == null) { |
|
|
|
* @param requestClientOption client参数选项 |
|
|
|
singletonOkhttp = newOkhttpClient(options); |
|
|
|
* @return okhttpClient |
|
|
|
} |
|
|
|
*/ |
|
|
|
} |
|
|
|
private OkHttpClient getOkhttpClient(RequestClientOption requestClientOption) { |
|
|
|
} |
|
|
|
// 单例模式直接使用root client
|
|
|
|
|
|
|
|
if (useSingletonClient) { |
|
|
|
return singletonOkhttp; |
|
|
|
return rootClient; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private OkHttpClient getOkhttpClient(RequestClientOption options) { |
|
|
|
return buildClient(rootClient.newBuilder(), requestClientOption); |
|
|
|
if (singletonClient) { |
|
|
|
|
|
|
|
return getSingletonClientInstant(options); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return newOkhttpClient(options); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* json的content-type |
|
|
|
|
|
|
|
*/ |
|
|
|
private static final String JSON_CONTENT_TYPE = "application/json; charset=utf-8"; |
|
|
|
private static final String JSON_CONTENT_TYPE = "application/json; charset=utf-8"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 默认的content-type(form) |
|
|
|
|
|
|
|
*/ |
|
|
|
private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded"; |
|
|
|
private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded"; |
|
|
|
|
|
|
|
|
|
|
|
public enum RequestMethod { |
|
|
|
/** |
|
|
|
GET, POST |
|
|
|
* 请求方法 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private enum RequestMethod { |
|
|
|
|
|
|
|
GET, POST, PUT, DELETE |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 根据请求header判断并获取MediaType |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param httpRequest 请求 |
|
|
|
|
|
|
|
* @return MediaType |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private MediaType getMediaType(HttpRequest httpRequest) { |
|
|
|
|
|
|
|
// 按照指定的Content-Type设置MediaType,不指定的话按form处理
|
|
|
|
|
|
|
|
String contentType = httpRequest.getHeaders().get("Content-Type"); |
|
|
|
|
|
|
|
MediaType mediaType; |
|
|
|
|
|
|
|
if (!"".equals(contentType)) { |
|
|
|
|
|
|
|
return MediaType.get(contentType); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return MediaType.get(DEFAULT_CONTENT_TYPE); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 根据请求信息来构建Request |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param requestMethod 请求方法,可选值:GET, POST, PUT, DELETE |
|
|
|
|
|
|
|
* @param url 地址 |
|
|
|
|
|
|
|
* @param httpRequest 请求体 |
|
|
|
|
|
|
|
* @return Request |
|
|
|
|
|
|
|
*/ |
|
|
|
private Request buildRequest(RequestMethod requestMethod, URL url, HttpRequest httpRequest) { |
|
|
|
private Request buildRequest(RequestMethod requestMethod, URL url, HttpRequest httpRequest) { |
|
|
|
Request.Builder requestBuilder = new Request.Builder().url(url); |
|
|
|
Request.Builder requestBuilder = new Request.Builder().url(url); |
|
|
|
|
|
|
|
|
|
|
@ -98,22 +186,32 @@ public class SimpleOkhttpRequester implements Requester { |
|
|
|
|
|
|
|
|
|
|
|
if (requestMethod == RequestMethod.GET) { |
|
|
|
if (requestMethod == RequestMethod.GET) { |
|
|
|
requestBuilder.get(); |
|
|
|
requestBuilder.get(); |
|
|
|
|
|
|
|
|
|
|
|
} else if (requestMethod == RequestMethod.POST) { |
|
|
|
} else if (requestMethod == RequestMethod.POST) { |
|
|
|
// 按照指定的Content-Type设置MediaType,不指定的话按form处理
|
|
|
|
requestBuilder.post(RequestBody.create(this.getMediaType(httpRequest), httpRequest.getData())); |
|
|
|
String contentType = httpRequest.getHeaders().get("Content-Type"); |
|
|
|
|
|
|
|
MediaType mediaType; |
|
|
|
} else if (requestMethod == RequestMethod.PUT) { |
|
|
|
if (!"".equals(contentType)) { |
|
|
|
requestBuilder.put(RequestBody.create(this.getMediaType(httpRequest), httpRequest.getData())); |
|
|
|
mediaType = MediaType.get(contentType); |
|
|
|
|
|
|
|
|
|
|
|
} else if (requestMethod == RequestMethod.DELETE) { |
|
|
|
|
|
|
|
if (httpRequest.getData() != null) { |
|
|
|
|
|
|
|
requestBuilder.delete(RequestBody.create(this.getMediaType(httpRequest), httpRequest.getData())); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
mediaType = MediaType.get(DEFAULT_CONTENT_TYPE); |
|
|
|
requestBuilder.delete(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
requestBuilder.post(RequestBody.create(mediaType, httpRequest.getData())); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return requestBuilder.build(); |
|
|
|
return requestBuilder.build(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 发送请求并获取响应,包装成HttpResponse |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param client okhttpClient |
|
|
|
|
|
|
|
* @param request 请求 |
|
|
|
|
|
|
|
* @return 包装好的Http响应 |
|
|
|
|
|
|
|
* @throws IOException 如果网络请求有异常 |
|
|
|
|
|
|
|
*/ |
|
|
|
private HttpResponse sendRequest(OkHttpClient client, Request request) throws IOException { |
|
|
|
private HttpResponse sendRequest(OkHttpClient client, Request request) throws IOException { |
|
|
|
try (Response response = client.newCall(request).execute()) { |
|
|
|
try (Response response = client.newCall(request).execute()) { |
|
|
|
HttpResponse httpResponse = new HttpResponse(); |
|
|
|
HttpResponse httpResponse = new HttpResponse(); |
|
|
@ -126,19 +224,29 @@ public class SimpleOkhttpRequester implements Requester { |
|
|
|
|
|
|
|
|
|
|
|
ResponseBody body = response.body(); |
|
|
|
ResponseBody body = response.body(); |
|
|
|
if (body != null) { |
|
|
|
if (body != null) { |
|
|
|
// 如果响应太大的话会导致oom,但其实大部分的请求都不会oom,因此直接使用bytes()
|
|
|
|
// 如果响应太大的话会导致oom,但其实大部分的请求响应都不会大到导致oom,因此直接使用bytes()
|
|
|
|
// 如果请求响应特别大的话请用ByteStream
|
|
|
|
// 如果请求响应特别大的话请用ByteStream,或者自造轮子
|
|
|
|
httpResponse.setBody(body.bytes()); |
|
|
|
httpResponse.setBody(body.bytes()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
httpResponse.setCookies(response.header("Set-Cookie")); |
|
|
|
httpResponse.setCookies(response.header("Set-Cookie")); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
return httpResponse; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 执行请求 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param requestMethod 请求方法 |
|
|
|
|
|
|
|
* @param url 请求地址 |
|
|
|
|
|
|
|
* @param httpRequest 请求体 |
|
|
|
|
|
|
|
* @param requestClientOption 请求响应 |
|
|
|
|
|
|
|
* @return 包装好的Http响应 |
|
|
|
|
|
|
|
* @throws IOException 如果网络请求有异常 |
|
|
|
|
|
|
|
*/ |
|
|
|
private HttpResponse doRequest(RequestMethod requestMethod, URL url, HttpRequest httpRequest, RequestClientOption requestClientOption) throws IOException { |
|
|
|
private HttpResponse doRequest(RequestMethod requestMethod, URL url, HttpRequest httpRequest, RequestClientOption requestClientOption) throws IOException { |
|
|
|
OkHttpClient client = this.getOkhttpClient(requestClientOption); |
|
|
|
OkHttpClient client = getOkhttpClient(requestClientOption); |
|
|
|
Request request = this.buildRequest(requestMethod, url, httpRequest); |
|
|
|
Request request = this.buildRequest(requestMethod, url, httpRequest); |
|
|
|
|
|
|
|
|
|
|
|
return this.sendRequest(client, request); |
|
|
|
return this.sendRequest(client, request); |
|
|
@ -150,16 +258,26 @@ public class SimpleOkhttpRequester implements Requester { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public HttpResponse post(URL url, HttpRequest request, RequestClientOption options) throws IOException { |
|
|
|
public HttpResponse post(URL url, HttpRequest request, RequestClientOption requestClientOption) throws IOException { |
|
|
|
return this.doRequest(RequestMethod.POST, url, request, options); |
|
|
|
return this.doRequest(RequestMethod.POST, url, request, requestClientOption); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public HttpResponse postJson(URL url, HttpRequest request, Object requestBody, RequestClientOption options) throws IOException { |
|
|
|
public HttpResponse postJson(URL url, HttpRequest request, Object requestBody, RequestClientOption requestClientOption) throws IOException { |
|
|
|
String json = new ObjectMapper().writeValueAsString(requestBody); |
|
|
|
String json = new ObjectMapper().writeValueAsString(requestBody); |
|
|
|
request.setData(json.getBytes(StandardCharsets.UTF_8)); |
|
|
|
request.setData(json.getBytes(StandardCharsets.UTF_8)); |
|
|
|
request.getHeaders().put("Content-Type", JSON_CONTENT_TYPE); |
|
|
|
request.getHeaders().put("Content-Type", JSON_CONTENT_TYPE); |
|
|
|
|
|
|
|
|
|
|
|
return this.doRequest(RequestMethod.POST, url, request, options); |
|
|
|
return this.doRequest(RequestMethod.POST, url, request, requestClientOption); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public HttpResponse put(URL url, HttpRequest request, RequestClientOption requestClientOption) throws IOException { |
|
|
|
|
|
|
|
return this.doRequest(RequestMethod.PUT, url, request, requestClientOption); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public HttpResponse delete(URL url, HttpRequest request, RequestClientOption requestClientOption) throws IOException { |
|
|
|
|
|
|
|
return this.doRequest(RequestMethod.DELETE, url, request, requestClientOption); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|