This commit is contained in:
Looly 2022-10-27 13:48:57 +08:00
parent f189c5e027
commit e1a2eaafa6
14 changed files with 279 additions and 246 deletions

View File

@ -0,0 +1,112 @@
package cn.hutool.core.io.stream;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.text.StrUtil;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 同步流可将包装的流同步为ByteArrayInputStream以便持有内容并关闭原流
*
* @author looly
* @since 6.0.0
*/
public class SyncInputStream extends FilterInputStream {
private final long length;
private final boolean isIgnoreEOFError;
/**
* 是否异步异步下只持有流否则将在初始化时直接读取body内容
*/
private volatile boolean asyncFlag = true;
/**
* 构造<br>
* 如果isAsync为{@code true}则直接持有原有流{@code false}则将流中内容按照给定length读到{@link ByteArrayInputStream}中备用
*
* @param in 数据流
* @param length 限定长度-1表示未知长度
* @param isAsync 是否异步
* @param isIgnoreEOFError 是否忽略EOF错误在Http协议中对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束<br>
* 如果服务端未遵循这个规范或响应没有正常结束会报EOF异常此选项用于是否忽略这个异常<br>
*/
public SyncInputStream(final InputStream in, final long length, final boolean isAsync, final boolean isIgnoreEOFError) {
super(in);
this.length = length;
this.isIgnoreEOFError = isIgnoreEOFError;
if (false == isAsync) {
sync();
}
}
/**
* 同步数据到内存
*/
public void sync() {
if (false == asyncFlag) {
// 已经是同步模式
return;
}
this.in = new ByteArrayInputStream(readBytes());
this.asyncFlag = false;
}
/**
* 读取流中所有bytes
*
* @return bytes
*/
public byte[] readBytes() {
final FastByteArrayOutputStream bytesOut = new FastByteArrayOutputStream(length > 0 ? (int)length : 1024);
final long length = copyTo(bytesOut, null);
return length > 0 ? bytesOut.toByteArray() : new byte[0];
}
/**
* 将流的内容拷贝到输出流
* @param out 输出流
* @param streamProgress 进度条
* @return 拷贝长度
*/
public long copyTo(final OutputStream out, final StreamProgress streamProgress){
long copyLength = -1;
try {
copyLength = IoUtil.copy(this.in, out, IoUtil.DEFAULT_BUFFER_SIZE, this.length, streamProgress);
} catch (final IORuntimeException e) {
if (false == (isIgnoreEOFError && isEOFException(e.getCause()))) {
throw e;
}
// 忽略读取流中的EOF错误
}finally {
// 读取结束
IoUtil.close(in);
}
return copyLength;
}
/**
* 是否为EOF异常包括<br>
* <ul>
* <li>FileNotFoundException服务端无返回内容</li>
* <li>EOFExceptionEOF异常</li>
* </ul>
*
* @param e 异常
* @return 是否EOF异常
*/
private static boolean isEOFException(final Throwable e) {
if (e instanceof FileNotFoundException) {
// 服务器无返回内容忽略之
return true;
}
return e instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF");
}
}

View File

@ -73,5 +73,11 @@
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -704,7 +704,7 @@ public class HttpUtil {
* @param conn HTTP连接对象
* @return 字符集
*/
public static String getCharset(final HttpURLConnection conn) {
public static Charset getCharset(final HttpURLConnection conn) {
if (conn == null) {
return null;
}
@ -719,7 +719,19 @@ public class HttpUtil {
* @return 字符集
* @since 5.2.6
*/
public static String getCharset(final String contentType) {
public static Charset getCharset(final String contentType) {
return CharsetUtil.parse(getCharsetName(contentType), null);
}
/**
* 从Http连接的头信息中获得字符集<br>
* 从ContentType中获取
*
* @param contentType Content-Type
* @return 字符集
* @since 5.2.6
*/
public static String getCharsetName(final String contentType) {
if (StrUtil.isBlank(contentType)) {
return null;
}

View File

@ -1,7 +1,6 @@
package cn.hutool.http.client;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
@ -37,11 +36,13 @@ public interface Response extends Closeable {
String header(final String name);
/**
* 获取字符集编码
* 获取字符集编码默认为响应头中的编码
*
* @return 字符集
*/
Charset charset();
default Charset charset(){
return HttpUtil.getCharset(header(Header.CONTENT_TYPE));
}
/**
* 获得服务区响应流<br>
@ -56,7 +57,7 @@ public interface Response extends Closeable {
* @return {@link ResponseBody}
*/
default ResponseBody body(){
return new ResponseBody(this, true);
return new ResponseBody(this, bodyStream(), false, true);
}
/**
@ -76,7 +77,7 @@ public interface Response extends Closeable {
* @return byte[]
*/
default byte[] bodyBytes() {
return IoUtil.readBytes(bodyStream());
return body().getBytes();
}
/**

View File

@ -1,10 +1,10 @@
package cn.hutool.http.client.body;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.stream.SyncInputStream;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
@ -13,8 +13,9 @@ import cn.hutool.http.HttpException;
import cn.hutool.http.client.Response;
import cn.hutool.http.meta.Header;
import java.io.EOFException;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -23,25 +24,25 @@ import java.io.OutputStream;
*
* @author looly
*/
public class ResponseBody implements HttpBody {
public class ResponseBody implements HttpBody, Closeable {
private final Response response;
/**
* 是否忽略响应读取时可能的EOF异常<br>
* 在Http协议中对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束<br>
* 如果服务端未遵循这个规范或响应没有正常结束会报EOF异常此选项用于是否忽略这个异常
* Http请求原始流
*/
private final boolean isIgnoreEOFError;
private final SyncInputStream bodyStream;
/**
* 构造
*
* @param response 响应体
* @param in HTTP主体响应流
* @param isAsync 是否异步模式
* @param isIgnoreEOFError 是否忽略EOF错误
*/
public ResponseBody(final Response response, final boolean isIgnoreEOFError) {
public ResponseBody(final Response response, final InputStream in, final boolean isAsync, final boolean isIgnoreEOFError) {
this.response = response;
this.isIgnoreEOFError = isIgnoreEOFError;
this.bodyStream = new SyncInputStream(in, response.contentLength(), isAsync, isIgnoreEOFError);
}
@Override
@ -51,7 +52,7 @@ public class ResponseBody implements HttpBody {
@Override
public InputStream getStream() {
return response.bodyStream();
return this.bodyStream;
}
@Override
@ -59,6 +60,25 @@ public class ResponseBody implements HttpBody {
write(out, false, null);
}
/**
* 同步数据到内存以bytes形式存储
*
* @return this
*/
public ResponseBody sync() {
this.bodyStream.sync();
return this;
}
/**
* 获取响应内容的bytes
*
* @return 响应内容bytes
*/
public byte[] getBytes() {
return this.bodyStream.readBytes();
}
/**
* 将响应内容写出到{@link OutputStream}<br>
* 异步模式下直接读取Http流写出同步模式下将存储在内存中的响应内容写出<br>
@ -72,9 +92,8 @@ public class ResponseBody implements HttpBody {
*/
public long write(final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) {
Assert.notNull(out, "[out] must be not null!");
final long contentLength = response.contentLength();
try {
return copyBody(getStream(), out, contentLength, streamProgress, isIgnoreEOFError);
return this.bodyStream.copyTo(out, streamProgress);
} finally {
if (isCloseOut) {
IoUtil.close(out);
@ -165,6 +184,11 @@ public class ResponseBody implements HttpBody {
return outFile;
}
@Override
public void close() throws IOException {
this.bodyStream.close();
}
// region ---------------------------------------------------------------------------- Private Methods
/**
@ -207,37 +231,5 @@ public class ResponseBody implements HttpBody {
}
return fileName;
}
/**
* 将响应内容写出到{@link OutputStream}<br>
* 异步模式下直接读取Http流写出同步模式下将存储在内存中的响应内容写出<br>
* 写出后会关闭Http流异步模式
*
* @param in 输入流
* @param out 写出的流
* @param contentLength 总长度-1表示未知
* @param streamProgress 进度显示接口通过实现此接口显示下载进度
* @param isIgnoreEOFError 是否忽略响应读取时可能的EOF异常
* @return 拷贝长度
*/
private static long copyBody(final InputStream in, final OutputStream out, final long contentLength, final StreamProgress streamProgress, final boolean isIgnoreEOFError) {
if (null == out) {
throw new NullPointerException("[out] is null!");
}
long copyLength = -1;
try {
copyLength = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress);
} catch (final IORuntimeException e) {
//noinspection StatementWithEmptyBody
if (isIgnoreEOFError
&& (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF"))) {
// 忽略读取HTTP流中的EOF错误
} else {
throw e;
}
}
return copyLength;
}
// endregion ---------------------------------------------------------------------------- Private Methods
}

View File

@ -8,7 +8,6 @@ import cn.hutool.http.client.Response;
import org.apache.http.Header;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
@ -67,8 +66,7 @@ public class HttpClient4Response implements Response {
@Override
public Charset charset() {
final Charset charset = ContentType.parse(rawRes.getEntity().getContentType().getValue()).getCharset();
return ObjUtil.defaultIfNull(charset, requestCharset);
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
}
@Override

View File

@ -6,7 +6,6 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.client.Response;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
@ -67,8 +66,7 @@ public class HttpClient5Response implements Response {
@Override
public Charset charset() {
final Charset charset = ContentType.parse(rawRes.getEntity().getContentType()).getCharset();
return ObjUtil.defaultIfNull(charset, requestCharset);
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
}
@Override

View File

@ -5,7 +5,6 @@ import cn.hutool.core.reflect.FieldUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.client.HeaderOperation;
import cn.hutool.http.meta.Method;
import cn.hutool.http.ssl.DefaultSSLInfo;
@ -20,8 +19,6 @@ import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.List;
import java.util.Map;
@ -403,38 +400,6 @@ public class HttpConnection implements HeaderOperation<HttpConnection> {
return 0;
}
/**
* 获得字符集编码<br>
* 从Http连接的头信息中获得字符集<br>
* 从ContentType中获取
*
* @return 字符集编码
*/
public String getCharsetName() {
return HttpUtil.getCharset(conn);
}
/**
* 获取字符集编码<br>
* 从Http连接的头信息中获得字符集<br>
* 从ContentType中获取
*
* @return {@link Charset}编码
* @since 3.0.9
*/
public Charset getCharset() {
Charset charset = null;
final String charsetName = getCharsetName();
if (StrUtil.isNotBlank(charsetName)) {
try {
charset = Charset.forName(charsetName);
} catch (final UnsupportedCharsetException e) {
// ignore
}
}
return charset;
}
@Override
public String toString() {
final StringBuilder sb = StrUtil.builder();

View File

@ -50,6 +50,7 @@ import java.util.function.Function;
*
* @author Looly
*/
@Deprecated
public class HttpRequest extends HttpBase<HttpRequest> {
// ---------------------------------------------------------------- static Http Method start

View File

@ -1,22 +1,24 @@
package cn.hutool.http.client.engine.jdk;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.stream.FastByteArrayOutputStream;
import cn.hutool.core.io.stream.EmptyInputStream;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.client.Response;
import cn.hutool.http.client.body.ResponseBody;
import cn.hutool.http.client.cookie.GlobalCookieManager;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpCookie;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
@ -25,55 +27,48 @@ import java.util.Map.Entry;
*
* @author Looly
*/
public class HttpResponse extends HttpBase<HttpResponse> implements Response, Closeable {
public class HttpResponse implements Response, Closeable {
/**
* 请求时的默认编码
*/
private final Charset requestCharset;
/**
* 响应内容体{@code null} 表示无内容
*/
private ResponseBody body;
private Map<String, List<String>> headers;
/**
* 是否忽略响应读取时可能的EOF异常<br>
* 在Http协议中对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束<br>
* 如果服务端未遵循这个规范或响应没有正常结束会报EOF异常此选项用于是否忽略这个异常
*/
protected boolean ignoreEOFError;
private final boolean ignoreEOFError;
/**
* 持有连接对象
*/
protected HttpConnection httpConnection;
/**
* Http请求原始流
*/
protected InputStream in;
/**
* 是否异步异步下只持有流否则将在初始化时直接读取body内容
*/
private volatile boolean isAsync;
/**
* 响应状态码
*/
protected int status;
/**
* 是否忽略读取Http响应体
*/
private final boolean ignoreBody;
/**
* 从响应中获取的编码
*/
private Charset charsetFromResponse;
/**
* 构造
*
* @param httpConnection {@link HttpConnection}
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常
* @param charset 编码从请求编码中获取默认编码
* @param requestCharset 编码从请求编码中获取默认编码
* @param isAsync 是否异步
* @param isIgnoreBody 是否忽略读取响应体
*/
protected HttpResponse(final HttpConnection httpConnection, final boolean ignoreEOFError, final Charset charset, final boolean isAsync, final boolean isIgnoreBody) {
protected HttpResponse(final HttpConnection httpConnection, final boolean ignoreEOFError, final Charset requestCharset, final boolean isAsync, final boolean isIgnoreBody) {
this.httpConnection = httpConnection;
this.ignoreEOFError = ignoreEOFError;
this.charset = charset;
this.isAsync = isAsync;
this.ignoreBody = isIgnoreBody;
initWithDisconnect();
this.requestCharset = requestCharset;
init(isAsync, isIgnoreBody);
}
/**
@ -86,6 +81,29 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
return this.status;
}
@Override
public String header(final String name) {
final List<String> headerValues = this.headers.get(name);
if (ArrayUtil.isNotEmpty(headerValues)) {
return headerValues.get(0);
}
return null;
}
/**
* 获取headers
*
* @return Headers Map
*/
public Map<String, List<String>> headers() {
return Collections.unmodifiableMap(headers);
}
@Override
public Charset charset() {
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
}
/**
* 同步<br>
* 如果为异步状态则暂时不读取服务器中响应的内容而是持有Http链接的{@link InputStream}<br>
@ -94,7 +112,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
* @return this
*/
public HttpResponse sync() {
return this.isAsync ? forceSync() : this;
if (null != this.body) {
this.body.sync();
}
close();
return this;
}
// ---------------------------------------------------------------- Http Response Header start
@ -153,15 +175,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
*/
@Override
public InputStream bodyStream() {
if (isAsync) {
return this.in;
}
return new ByteArrayInputStream(this.bodyBytes);
// 使用ResponseBody中的stream有利于控制响应数据的同步与否
return null == this.body ? EmptyInputStream.INSTANCE : this.body.getStream();
}
@Override
public ResponseBody body() {
return new ResponseBody(this, this.ignoreEOFError);
return this.body;
}
/**
@ -174,30 +194,14 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
@Override
public byte[] bodyBytes() {
sync();
return this.bodyBytes;
}
/**
* 设置主体字节码一般用于拦截器修改响应内容<br>
* 需在此方法调用前使用charset方法设置编码否则使用默认编码UTF-8
*
* @param bodyBytes 主体
* @return this
*/
@SuppressWarnings("resource")
public HttpResponse body(final byte[] bodyBytes) {
sync();
if (null != bodyBytes) {
this.bodyBytes = bodyBytes;
}
return this;
return body().getBytes();
}
// ---------------------------------------------------------------- Body end
@Override
public void close() {
IoUtil.close(this.in);
this.in = null;
// 关闭流
IoUtil.close(this.body);
// 关闭连接
this.httpConnection.disconnectQuietly();
}
@ -211,39 +215,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
}
sb.append("Response Body: ").append(StrUtil.CRLF);
sb.append(" ").append(this.body()).append(StrUtil.CRLF);
sb.append(" ").append(this.bodyStr()).append(StrUtil.CRLF);
return sb.toString();
}
@Override
public String header(final String name) {
return super.header(name);
}
// ---------------------------------------------------------------- Private method start
/**
* 初始化Http响应并在报错时关闭连接<br>
* 初始化包括
*
* <pre>
* 1读取Http状态
* 2读取头信息
* 3持有Http流并不关闭流
* </pre>
*
* @throws HttpException IO异常
*/
private void initWithDisconnect() throws HttpException {
try {
init();
} catch (final HttpException e) {
this.httpConnection.disconnectQuietly();
throw e;
}
}
/**
* 初始化Http响应<br>
* 初始化包括
@ -257,7 +235,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
* @throws HttpException IO异常
*/
@SuppressWarnings("resource")
private void init() throws HttpException {
private void init(final boolean isAsync, final boolean isIgnoreBody) throws HttpException {
// 获取响应状态码
try {
this.status = httpConnection.getCode();
@ -276,73 +254,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
// StaticLog.warn(e, e.getMessage());
}
// 存储服务端设置的Cookie信息
GlobalCookieManager.store(httpConnection, this.headers);
// 获取响应编码如果非空替换用户定义的编码
final Charset charsetFromResponse = httpConnection.getCharset();
if (null != charsetFromResponse) {
this.charset = charsetFromResponse;
}
// 获取响应内容流
this.in = new HttpInputStream(this);
// 同步情况下强制同步
if (!this.isAsync) {
forceSync();
if (false == isIgnoreBody) {
this.body = new ResponseBody(this, new HttpInputStream(this), isAsync, this.ignoreEOFError);
}
}
/**
* 强制同步用于初始化<br>
* 强制同步后变化如下
*
* <pre>
* 1读取body内容到内存
* 2异步状态设为false变为同步状态
* 3关闭Http流
* 4断开与服务器连接
* </pre>
*
* @return this
*/
private HttpResponse forceSync() {
// 非同步状态转为同步状态
try {
this.readBody(this.in);
} catch (final IORuntimeException e) {
//noinspection StatementWithEmptyBody
if (e.getCause() instanceof FileNotFoundException) {
// 服务器无返回内容忽略之
} else {
throw new HttpException(e);
}
} finally {
if (this.isAsync) {
this.isAsync = false;
}
this.close();
}
return this;
}
/**
* 读取主体忽略EOFException异常
*
* @param in 输入流
* @throws IORuntimeException IO异常
*/
private void readBody(final InputStream in) throws IORuntimeException {
if (ignoreBody) {
return;
}
final long contentLength = contentLength();
final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int) contentLength);
body().writeClose(out);
this.bodyBytes = out.toByteArray();
}
// ---------------------------------------------------------------- Private method end
}

View File

@ -1,11 +1,8 @@
package cn.hutool.http.client.engine.okhttp;
import cn.hutool.core.io.stream.EmptyInputStream;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.client.Response;
import cn.hutool.http.meta.Header;
import okhttp3.ResponseBody;
import java.io.IOException;
@ -46,12 +43,7 @@ public class OkHttpResponse implements Response {
@Override
public Charset charset() {
final String contentType = rawRes.header(Header.CONTENT_TYPE.getValue());
if(StrUtil.isNotEmpty(contentType)){
final String charset = HttpUtil.getCharset(contentType);
CharsetUtil.parse(charset, this.requestCharset);
}
return this.requestCharset;
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
}
@Override

View File

@ -11,6 +11,7 @@ import cn.hutool.core.net.multipart.UploadSetting;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.meta.Header;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.meta.Method;
@ -169,8 +170,7 @@ public class HttpServerRequest extends HttpServerBase {
public Charset getCharset() {
if(null == this.charsetCache){
final String contentType = getContentType();
final String charsetStr = HttpUtil.getCharset(contentType);
this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET);
this.charsetCache = ObjUtil.defaultIfNull(HttpUtil.getCharset(contentType), DEFAULT_CHARSET);
}
return this.charsetCache;

View File

@ -0,0 +1,16 @@
package cn.hutool.http;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import java.io.IOException;
public class MockServerTest {
public static void main(final String[] args) throws IOException {
//noinspection resource
final MockWebServer server = new MockWebServer();
final MockResponse mockResponse = new MockResponse().setBody("hello, world!");
server.enqueue(mockResponse);
server.start(8080);
}
}

View File

@ -0,0 +1,22 @@
package cn.hutool.http.client;
import cn.hutool.core.lang.Console;
import cn.hutool.http.client.engine.jdk.JdkClientEngine;
import cn.hutool.http.meta.Method;
import org.junit.Ignore;
import org.junit.Test;
public class JdkEngineTest {
@Test
@Ignore
public void getTest(){
final ClientEngine engine = new JdkClientEngine();
final Request req = Request.of("https://www.hutool.cn/").method(Method.GET);
final Response res = engine.send(req);
Console.log(res.getStatus());
Console.log(res.bodyStr());
}
}