mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
fix code
This commit is contained in:
parent
f189c5e027
commit
e1a2eaafa6
112
hutool-core/src/main/java/cn/hutool/core/io/stream/SyncInputStream.java
Executable file
112
hutool-core/src/main/java/cn/hutool/core/io/stream/SyncInputStream.java
Executable 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>EOFException:EOF异常</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");
|
||||||
|
}
|
||||||
|
}
|
@ -73,5 +73,11 @@
|
|||||||
<version>1.7.25</version>
|
<version>1.7.25</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<version>4.10.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -704,7 +704,7 @@ public class HttpUtil {
|
|||||||
* @param conn HTTP连接对象
|
* @param conn HTTP连接对象
|
||||||
* @return 字符集
|
* @return 字符集
|
||||||
*/
|
*/
|
||||||
public static String getCharset(final HttpURLConnection conn) {
|
public static Charset getCharset(final HttpURLConnection conn) {
|
||||||
if (conn == null) {
|
if (conn == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -719,7 +719,19 @@ public class HttpUtil {
|
|||||||
* @return 字符集
|
* @return 字符集
|
||||||
* @since 5.2.6
|
* @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)) {
|
if (StrUtil.isBlank(contentType)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package cn.hutool.http.client;
|
package cn.hutool.http.client;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
@ -37,11 +36,13 @@ public interface Response extends Closeable {
|
|||||||
String header(final String name);
|
String header(final String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取字符集编码
|
* 获取字符集编码,默认为响应头中的编码
|
||||||
*
|
*
|
||||||
* @return 字符集
|
* @return 字符集
|
||||||
*/
|
*/
|
||||||
Charset charset();
|
default Charset charset(){
|
||||||
|
return HttpUtil.getCharset(header(Header.CONTENT_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得服务区响应流<br>
|
* 获得服务区响应流<br>
|
||||||
@ -56,7 +57,7 @@ public interface Response extends Closeable {
|
|||||||
* @return {@link ResponseBody}
|
* @return {@link ResponseBody}
|
||||||
*/
|
*/
|
||||||
default ResponseBody body(){
|
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[]
|
* @return byte[]
|
||||||
*/
|
*/
|
||||||
default byte[] bodyBytes() {
|
default byte[] bodyBytes() {
|
||||||
return IoUtil.readBytes(bodyStream());
|
return body().getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package cn.hutool.http.client.body;
|
package cn.hutool.http.client.body;
|
||||||
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.io.IORuntimeException;
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.io.StreamProgress;
|
import cn.hutool.core.io.StreamProgress;
|
||||||
import cn.hutool.core.io.file.FileNameUtil;
|
import cn.hutool.core.io.file.FileNameUtil;
|
||||||
|
import cn.hutool.core.io.stream.SyncInputStream;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.regex.ReUtil;
|
import cn.hutool.core.regex.ReUtil;
|
||||||
import cn.hutool.core.text.StrUtil;
|
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.client.Response;
|
||||||
import cn.hutool.http.meta.Header;
|
import cn.hutool.http.meta.Header;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@ -23,25 +24,25 @@ import java.io.OutputStream;
|
|||||||
*
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
*/
|
*/
|
||||||
public class ResponseBody implements HttpBody {
|
public class ResponseBody implements HttpBody, Closeable {
|
||||||
|
|
||||||
private final Response response;
|
private final Response response;
|
||||||
/**
|
/**
|
||||||
* 是否忽略响应读取时可能的EOF异常。<br>
|
* Http请求原始流
|
||||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
|
||||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
|
||||||
*/
|
*/
|
||||||
private final boolean isIgnoreEOFError;
|
private final SyncInputStream bodyStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param response 响应体
|
* @param response 响应体
|
||||||
|
* @param in HTTP主体响应流
|
||||||
|
* @param isAsync 是否异步模式
|
||||||
* @param isIgnoreEOFError 是否忽略EOF错误
|
* @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.response = response;
|
||||||
this.isIgnoreEOFError = isIgnoreEOFError;
|
this.bodyStream = new SyncInputStream(in, response.contentLength(), isAsync, isIgnoreEOFError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -51,7 +52,7 @@ public class ResponseBody implements HttpBody {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getStream() {
|
public InputStream getStream() {
|
||||||
return response.bodyStream();
|
return this.bodyStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -59,6 +60,25 @@ public class ResponseBody implements HttpBody {
|
|||||||
write(out, false, null);
|
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>
|
* 将响应内容写出到{@link OutputStream}<br>
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||||
@ -72,9 +92,8 @@ public class ResponseBody implements HttpBody {
|
|||||||
*/
|
*/
|
||||||
public long write(final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) {
|
public long write(final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) {
|
||||||
Assert.notNull(out, "[out] must be not null!");
|
Assert.notNull(out, "[out] must be not null!");
|
||||||
final long contentLength = response.contentLength();
|
|
||||||
try {
|
try {
|
||||||
return copyBody(getStream(), out, contentLength, streamProgress, isIgnoreEOFError);
|
return this.bodyStream.copyTo(out, streamProgress);
|
||||||
} finally {
|
} finally {
|
||||||
if (isCloseOut) {
|
if (isCloseOut) {
|
||||||
IoUtil.close(out);
|
IoUtil.close(out);
|
||||||
@ -165,6 +184,11 @@ public class ResponseBody implements HttpBody {
|
|||||||
return outFile;
|
return outFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
this.bodyStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
// region ---------------------------------------------------------------------------- Private Methods
|
// region ---------------------------------------------------------------------------- Private Methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,37 +231,5 @@ public class ResponseBody implements HttpBody {
|
|||||||
}
|
}
|
||||||
return fileName;
|
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
|
// endregion ---------------------------------------------------------------------------- Private Methods
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import cn.hutool.http.client.Response;
|
|||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
import org.apache.http.ParseException;
|
import org.apache.http.ParseException;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.entity.ContentType;
|
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -67,8 +66,7 @@ public class HttpClient4Response implements Response {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Charset charset() {
|
public Charset charset() {
|
||||||
final Charset charset = ContentType.parse(rawRes.getEntity().getContentType().getValue()).getCharset();
|
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
|
||||||
return ObjUtil.defaultIfNull(charset, requestCharset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -6,7 +6,6 @@ import cn.hutool.core.util.ObjUtil;
|
|||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.client.Response;
|
import cn.hutool.http.client.Response;
|
||||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
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.Header;
|
||||||
import org.apache.hc.core5.http.ParseException;
|
import org.apache.hc.core5.http.ParseException;
|
||||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||||
@ -67,8 +66,7 @@ public class HttpClient5Response implements Response {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Charset charset() {
|
public Charset charset() {
|
||||||
final Charset charset = ContentType.parse(rawRes.getEntity().getContentType()).getCharset();
|
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
|
||||||
return ObjUtil.defaultIfNull(charset, requestCharset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -5,7 +5,6 @@ import cn.hutool.core.reflect.FieldUtil;
|
|||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.HttpUtil;
|
|
||||||
import cn.hutool.http.client.HeaderOperation;
|
import cn.hutool.http.client.HeaderOperation;
|
||||||
import cn.hutool.http.meta.Method;
|
import cn.hutool.http.meta.Method;
|
||||||
import cn.hutool.http.ssl.DefaultSSLInfo;
|
import cn.hutool.http.ssl.DefaultSSLInfo;
|
||||||
@ -20,8 +19,6 @@ import java.net.HttpURLConnection;
|
|||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.UnsupportedCharsetException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -403,38 +400,6 @@ public class HttpConnection implements HeaderOperation<HttpConnection> {
|
|||||||
return 0;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder sb = StrUtil.builder();
|
final StringBuilder sb = StrUtil.builder();
|
||||||
|
@ -50,6 +50,7 @@ import java.util.function.Function;
|
|||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class HttpRequest extends HttpBase<HttpRequest> {
|
public class HttpRequest extends HttpBase<HttpRequest> {
|
||||||
|
|
||||||
// ---------------------------------------------------------------- static Http Method start
|
// ---------------------------------------------------------------- static Http Method start
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
package cn.hutool.http.client.engine.jdk;
|
package cn.hutool.http.client.engine.jdk;
|
||||||
|
|
||||||
import cn.hutool.core.io.IORuntimeException;
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
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.text.StrUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.client.Response;
|
import cn.hutool.http.client.Response;
|
||||||
import cn.hutool.http.client.body.ResponseBody;
|
import cn.hutool.http.client.body.ResponseBody;
|
||||||
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,55 +27,48 @@ import java.util.Map.Entry;
|
|||||||
*
|
*
|
||||||
* @author Looly
|
* @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>
|
* 是否忽略响应读取时可能的EOF异常。<br>
|
||||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||||
*/
|
*/
|
||||||
protected boolean ignoreEOFError;
|
private final boolean ignoreEOFError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 持有连接对象
|
* 持有连接对象
|
||||||
*/
|
*/
|
||||||
protected HttpConnection httpConnection;
|
protected HttpConnection httpConnection;
|
||||||
/**
|
|
||||||
* Http请求原始流
|
|
||||||
*/
|
|
||||||
protected InputStream in;
|
|
||||||
/**
|
|
||||||
* 是否异步,异步下只持有流,否则将在初始化时直接读取body内容
|
|
||||||
*/
|
|
||||||
private volatile boolean isAsync;
|
|
||||||
/**
|
/**
|
||||||
* 响应状态码
|
* 响应状态码
|
||||||
*/
|
*/
|
||||||
protected int status;
|
protected int status;
|
||||||
/**
|
|
||||||
* 是否忽略读取Http响应体
|
|
||||||
*/
|
|
||||||
private final boolean ignoreBody;
|
|
||||||
/**
|
|
||||||
* 从响应中获取的编码
|
|
||||||
*/
|
|
||||||
private Charset charsetFromResponse;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param httpConnection {@link HttpConnection}
|
* @param httpConnection {@link HttpConnection}
|
||||||
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常
|
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常
|
||||||
* @param charset 编码,从请求编码中获取默认编码
|
* @param requestCharset 编码,从请求编码中获取默认编码
|
||||||
* @param isAsync 是否异步
|
* @param isAsync 是否异步
|
||||||
* @param isIgnoreBody 是否忽略读取响应体
|
* @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.httpConnection = httpConnection;
|
||||||
this.ignoreEOFError = ignoreEOFError;
|
this.ignoreEOFError = ignoreEOFError;
|
||||||
this.charset = charset;
|
this.requestCharset = requestCharset;
|
||||||
this.isAsync = isAsync;
|
init(isAsync, isIgnoreBody);
|
||||||
this.ignoreBody = isIgnoreBody;
|
|
||||||
initWithDisconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +81,29 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
|
|||||||
return this.status;
|
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>
|
* 同步<br>
|
||||||
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
||||||
@ -94,7 +112,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
|
|||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public HttpResponse sync() {
|
public HttpResponse sync() {
|
||||||
return this.isAsync ? forceSync() : this;
|
if (null != this.body) {
|
||||||
|
this.body.sync();
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Http Response Header start
|
// ---------------------------------------------------------------- Http Response Header start
|
||||||
@ -153,15 +175,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public InputStream bodyStream() {
|
public InputStream bodyStream() {
|
||||||
if (isAsync) {
|
// 使用ResponseBody中的stream有利于控制响应数据的同步与否
|
||||||
return this.in;
|
return null == this.body ? EmptyInputStream.INSTANCE : this.body.getStream();
|
||||||
}
|
|
||||||
return new ByteArrayInputStream(this.bodyBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseBody body() {
|
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
|
@Override
|
||||||
public byte[] bodyBytes() {
|
public byte[] bodyBytes() {
|
||||||
sync();
|
sync();
|
||||||
return this.bodyBytes;
|
return body().getBytes();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置主体字节码,一般用于拦截器修改响应内容<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;
|
|
||||||
}
|
}
|
||||||
// ---------------------------------------------------------------- Body end
|
// ---------------------------------------------------------------- Body end
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
IoUtil.close(this.in);
|
// 关闭流
|
||||||
this.in = null;
|
IoUtil.close(this.body);
|
||||||
// 关闭连接
|
// 关闭连接
|
||||||
this.httpConnection.disconnectQuietly();
|
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("Response Body: ").append(StrUtil.CRLF);
|
||||||
sb.append(" ").append(this.body()).append(StrUtil.CRLF);
|
sb.append(" ").append(this.bodyStr()).append(StrUtil.CRLF);
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String header(final String name) {
|
|
||||||
return super.header(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Private method start
|
// ---------------------------------------------------------------- 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>
|
* 初始化Http响应<br>
|
||||||
* 初始化包括:
|
* 初始化包括:
|
||||||
@ -257,7 +235,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
|
|||||||
* @throws HttpException IO异常
|
* @throws HttpException IO异常
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
private void init() throws HttpException {
|
private void init(final boolean isAsync, final boolean isIgnoreBody) throws HttpException {
|
||||||
// 获取响应状态码
|
// 获取响应状态码
|
||||||
try {
|
try {
|
||||||
this.status = httpConnection.getCode();
|
this.status = httpConnection.getCode();
|
||||||
@ -276,73 +254,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Response, Cl
|
|||||||
// StaticLog.warn(e, e.getMessage());
|
// StaticLog.warn(e, e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 存储服务端设置的Cookie信息
|
// 存储服务端设置的Cookie信息
|
||||||
GlobalCookieManager.store(httpConnection, this.headers);
|
GlobalCookieManager.store(httpConnection, this.headers);
|
||||||
|
|
||||||
// 获取响应编码,如果非空,替换用户定义的编码
|
|
||||||
final Charset charsetFromResponse = httpConnection.getCharset();
|
|
||||||
if (null != charsetFromResponse) {
|
|
||||||
this.charset = charsetFromResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取响应内容流
|
// 获取响应内容流
|
||||||
this.in = new HttpInputStream(this);
|
if (false == isIgnoreBody) {
|
||||||
|
this.body = new ResponseBody(this, new HttpInputStream(this), isAsync, this.ignoreEOFError);
|
||||||
// 同步情况下强制同步
|
|
||||||
if (!this.isAsync) {
|
|
||||||
forceSync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制同步,用于初始化<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
|
// ---------------------------------------------------------------- Private method end
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package cn.hutool.http.client.engine.okhttp;
|
package cn.hutool.http.client.engine.okhttp;
|
||||||
|
|
||||||
import cn.hutool.core.io.stream.EmptyInputStream;
|
import cn.hutool.core.io.stream.EmptyInputStream;
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
|
||||||
import cn.hutool.http.HttpUtil;
|
|
||||||
import cn.hutool.http.client.Response;
|
import cn.hutool.http.client.Response;
|
||||||
import cn.hutool.http.meta.Header;
|
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -46,12 +43,7 @@ public class OkHttpResponse implements Response {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Charset charset() {
|
public Charset charset() {
|
||||||
final String contentType = rawRes.header(Header.CONTENT_TYPE.getValue());
|
return ObjUtil.defaultIfNull(Response.super.charset(), requestCharset);
|
||||||
if(StrUtil.isNotEmpty(contentType)){
|
|
||||||
final String charset = HttpUtil.getCharset(contentType);
|
|
||||||
CharsetUtil.parse(charset, this.requestCharset);
|
|
||||||
}
|
|
||||||
return this.requestCharset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -11,6 +11,7 @@ import cn.hutool.core.net.multipart.UploadSetting;
|
|||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.http.meta.Header;
|
import cn.hutool.http.meta.Header;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.hutool.http.meta.Method;
|
import cn.hutool.http.meta.Method;
|
||||||
@ -169,8 +170,7 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
public Charset getCharset() {
|
public Charset getCharset() {
|
||||||
if(null == this.charsetCache){
|
if(null == this.charsetCache){
|
||||||
final String contentType = getContentType();
|
final String contentType = getContentType();
|
||||||
final String charsetStr = HttpUtil.getCharset(contentType);
|
this.charsetCache = ObjUtil.defaultIfNull(HttpUtil.getCharset(contentType), DEFAULT_CHARSET);
|
||||||
this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.charsetCache;
|
return this.charsetCache;
|
||||||
|
16
hutool-http/src/test/java/cn/hutool/http/MockServerTest.java
Executable file
16
hutool-http/src/test/java/cn/hutool/http/MockServerTest.java
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
22
hutool-http/src/test/java/cn/hutool/http/client/JdkEngineTest.java
Executable file
22
hutool-http/src/test/java/cn/hutool/http/client/JdkEngineTest.java
Executable 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user