diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da685e96..3a9d27535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * ### 🐞Bug修复 * 【extra 】 修复TinyPinyinEngine空构造造成可能的误判问题 +* 【http 】 修复在gzip模式下Content-Length服务端设置异常导致的阶段 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java index 16bfda8fc..68d78b489 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java @@ -22,6 +22,9 @@ public class FastByteArrayOutputStream extends OutputStream { private final FastByteBuffer buffer; + /** + * 构造 + */ public FastByteArrayOutputStream() { this(1024); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastByteBuffer.java b/hutool-core/src/main/java/cn/hutool/core/io/FastByteBuffer.java index 17dae9af7..d2a7540f2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FastByteBuffer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FastByteBuffer.java @@ -40,10 +40,13 @@ public class FastByteBuffer { private final int minChunkLen; public FastByteBuffer() { - this.minChunkLen = 1024; + this(1024); } public FastByteBuffer(int size) { + if(size <= 0){ + size = 1024; + } this.minChunkLen = Math.abs(size); } @@ -282,4 +285,4 @@ public class FastByteBuffer { } } -} \ No newline at end of file +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index e8daa41a8..f8b2459d5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -102,7 +102,7 @@ public class IoUtil extends NioUtil { * @return 传输的byte数 * @throws IORuntimeException IO异常 */ - public static long copy(Reader reader, Writer writer, int bufferSize, int count, StreamProgress streamProgress) throws IORuntimeException { + public static long copy(Reader reader, Writer writer, int bufferSize, long count, StreamProgress streamProgress) throws IORuntimeException { return new ReaderWriterCopier(bufferSize, count, streamProgress).copy(reader, writer); } @@ -157,7 +157,7 @@ public class IoUtil extends NioUtil { * @throws IORuntimeException IO异常 * @since 5.7.8 */ - public static long copy(InputStream in, OutputStream out, int bufferSize, int count, StreamProgress streamProgress) throws IORuntimeException { + public static long copy(InputStream in, OutputStream out, int bufferSize, long count, StreamProgress streamProgress) throws IORuntimeException { return new StreamCopier(bufferSize, count, streamProgress).copy(in, out); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index 752f712e9..b8823aeef 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -117,6 +117,26 @@ public class HttpResponse extends HttpBase implements Closeable { return header(Header.CONTENT_ENCODING); } + /** + * 获取内容长度,以下情况长度无效: + * + * 参考:https://blog.csdn.net/jiang7701037/article/details/86304302 + * + * @return 长度,-1表示服务端未返回或长度无效 + * @since 5.7.9 + */ + public long contentLength() { + long contentLength = Convert.toLong(header(Header.CONTENT_LENGTH), -1L); + if (contentLength > 0 && (isChunked() || StrUtil.isNotBlank(contentEncoding()))) { + //按照HTTP协议规范,在 Transfer-Encoding和Content-Encoding设置后 Content-Length 无效。 + contentLength = -1; + } + return contentLength; + } + /** * 是否为gzip压缩过的内容 * @@ -254,9 +274,9 @@ public class HttpResponse extends HttpBase implements Closeable { if (null == out) { throw new NullPointerException("[out] is null!"); } - final int contentLength = Convert.toInt(header(Header.CONTENT_LENGTH), -1); + final long contentLength = contentLength(); try { - return IoUtil.copyByNIO(bodyStream(), out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress); + return copyBody(bodyStream(), out, contentLength, streamProgress); } finally { IoUtil.close(this); if (isCloseOut) { @@ -452,33 +472,6 @@ public class HttpResponse extends HttpBase implements Closeable { return this.isAsync ? this : forceSync(); } - /** - * 读取主体,忽略EOFException异常 - * - * @param in 输入流 - * @throws IORuntimeException IO异常 - */ - private void readBody(InputStream in) throws IORuntimeException { - if (ignoreBody) { - return; - } - - final int contentLength = Convert.toInt(header(Header.CONTENT_LENGTH), -1); - final FastByteArrayOutputStream out = contentLength > 0 ? - new FastByteArrayOutputStream(contentLength) : new FastByteArrayOutputStream(); - try { - IoUtil.copy(in, out, -1, contentLength, null); - } catch (IORuntimeException e) { - //noinspection StatementWithEmptyBody - if (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF")) { - // 忽略读取HTTP流中的EOF错误 - } else { - throw e; - } - } - this.bodyBytes = out.toByteArray(); - } - /** * 强制同步,用于初始化
* 强制同步后变化如下: @@ -512,6 +505,53 @@ public class HttpResponse extends HttpBase implements Closeable { return this; } + /** + * 读取主体,忽略EOFException异常 + * + * @param in 输入流 + * @throws IORuntimeException IO异常 + */ + private void readBody(InputStream in) throws IORuntimeException { + if (ignoreBody) { + return; + } + + final long contentLength = contentLength(); + final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int)contentLength); + copyBody(in, out, contentLength, null); + this.bodyBytes = out.toByteArray(); + } + + /** + * 将响应内容写出到{@link OutputStream}
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭Http流(异步模式) + * + * @param in 输入流 + * @param out 写出的流 + * @param contentLength 总长度,-1表示未知 + * @param streamProgress 进度显示接口,通过实现此接口显示下载进度 + * @return 拷贝长度 + */ + private static long copyBody(InputStream in, OutputStream out, long contentLength, StreamProgress streamProgress) { + 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 (IORuntimeException e) { + //noinspection StatementWithEmptyBody + if (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF")) { + // 忽略读取HTTP流中的EOF错误 + } else { + throw e; + } + } + return copyLength; + } + /** * 从Content-Disposition头中获取文件名 * diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 70da72bfc..bce5cb4e3 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -19,7 +19,9 @@ public class HttpUtilTest { @Test @Ignore public void postTest() { - String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506").charset(CharsetUtil.UTF_8).execute().body(); + String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506") + .charset(CharsetUtil.UTF_8) + .execute().body(); Console.log(result); } @@ -45,6 +47,7 @@ public class HttpUtilTest { @Test @Ignore public void getTest2() { + // 此链接较为特殊,User-Agent去掉后进入一个JS跳转页面,如果设置了,需要开启302跳转 // 自定义的默认header无效 String result = HttpRequest .get("https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101457313&redirect_uri=http%3A%2F%2Fwww.benmovip.com%2Fpay-cloud%2Fqqlogin%2FgetCode&state=ok")