From 9e39667e5a218e8fdc056c3e20ab3bd5f0b37e3e Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 16 Aug 2019 15:04:42 +0800 Subject: [PATCH] add HttpInputStream --- CHANGELOG.md | 1 + .../cn/hutool/core/io/watch/WatchMonitor.java | 8 +- .../cn/hutool/extra/servlet/ServletUtil.java | 60 +++++++--- .../engine/analysis/AnalysisEngine.java | 2 +- .../tokenizer/engine/ansj/AnsjEngine.java | 4 +- .../tokenizer/engine/hanlp/HanLPEngine.java | 2 +- .../engine/ikanalyzer/IKAnalyzerEngine.java | 1 - .../java/cn/hutool/http/HttpInputStream.java | 111 ++++++++++++++++++ .../java/cn/hutool/http/HttpResponse.java | 32 ++--- .../cn/hutool/http/test/HttpUtilTest.java | 3 +- 10 files changed, 174 insertions(+), 50 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5eb4c70..d29d9ae16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【core】 强化ExceptionUtil(issue#459@Github) * 【core】 增强日期工具类(pr#455@Github) * 【setting】 构造Setting增加默认字符编码 +* 【extra】 ServletUtil增加getHeaderMap方法 ### Bug修复 * 【cache】 修复missCount规则(issue#465@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java index b813d09ca..1bb2ea4be 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java @@ -55,10 +55,10 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ public static final WatchEvent.Kind ENTRY_DELETE = StandardWatchEventKinds.ENTRY_DELETE; /** 全部事件 */ public static final WatchEvent.Kind[] EVENTS_ALL = {// - StandardWatchEventKinds.OVERFLOW, //事件丢失 - StandardWatchEventKinds.ENTRY_MODIFY, //修改 - StandardWatchEventKinds.ENTRY_CREATE, //创建 - StandardWatchEventKinds.ENTRY_DELETE //删除 + OVERFLOW, //事件丢失 + ENTRY_MODIFY, //修改 + ENTRY_CREATE, //创建 + ENTRY_DELETE //删除 }; /** 监听路径,必须为目录 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index 98ee5f60a..ff3235a23 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -5,9 +5,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; import java.io.Writer; import java.lang.reflect.Type; +import java.nio.charset.Charset; import java.util.Collections; import java.util.Date; import java.util.Enumeration; @@ -267,6 +267,27 @@ public class ServletUtil { } // --------------------------------------------------------- Header start + /** + * 获取请求所有的头(header)信息 + * + * @param request 请求对象{@link HttpServletRequest} + * @return header值 + * @since 4.6.2 + */ + public static Map getHeaderMap(HttpServletRequest request) { + final Map headerMap = new HashMap<>(); + + final Enumeration names = request.getHeaderNames(); + String name = null; + while (names.hasMoreElements()) { + name = names.nextElement(); + headerMap.put(name, request.getHeader(name)); + } + + return headerMap; + } + + /** * 忽略大小写获得请求header中的信息 * @@ -274,8 +295,8 @@ public class ServletUtil { * @param nameIgnoreCase 忽略大小写头信息的KEY * @return header值 */ - public final static String getHeaderIgnoreCase(HttpServletRequest request, String nameIgnoreCase) { - Enumeration names = request.getHeaderNames(); + public static String getHeaderIgnoreCase(HttpServletRequest request, String nameIgnoreCase) { + final Enumeration names = request.getHeaderNames(); String name = null; while (names.hasMoreElements()) { name = names.nextElement(); @@ -286,6 +307,18 @@ public class ServletUtil { return null; } + + /** + * 获得请求header中的信息 + * + * @param request 请求对象{@link HttpServletRequest} + * @param name 头信息的KEY + * @param charsetName 字符集 + * @return header值 + */ + public static String getHeader(HttpServletRequest request, String name, String charsetName) { + return getHeader(request, name, CharsetUtil.charset(charsetName)); + } /** * 获得请求header中的信息 @@ -294,15 +327,12 @@ public class ServletUtil { * @param name 头信息的KEY * @param charset 字符集 * @return header值 + * @since 4.6.2 */ - public final static String getHeader(HttpServletRequest request, String name, String charset) { + public static String getHeader(HttpServletRequest request, String name, Charset charset) { final String header = request.getHeader(name); if (null != header) { - try { - return new String(header.getBytes(CharsetUtil.ISO_8859_1), charset); - } catch (UnsupportedEncodingException e) { - throw new UtilException(StrUtil.format("Error charset {} for http request header.", charset)); - } + return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset); } return null; } @@ -375,7 +405,7 @@ public class ServletUtil { * @param name cookie名 * @return Cookie对象 */ - public final static Cookie getCookie(HttpServletRequest httpServletRequest, String name) { + public static Cookie getCookie(HttpServletRequest httpServletRequest, String name) { final Map cookieMap = readCookieMap(httpServletRequest); return cookieMap == null ? null : cookieMap.get(name); } @@ -386,7 +416,7 @@ public class ServletUtil { * @param httpServletRequest {@link HttpServletRequest} * @return Cookie map */ - public final static Map readCookieMap(HttpServletRequest httpServletRequest) { + public static Map readCookieMap(HttpServletRequest httpServletRequest) { Map cookieMap = new HashMap(); Cookie[] cookies = httpServletRequest.getCookies(); if (null == cookies) { @@ -404,7 +434,7 @@ public class ServletUtil { * @param response 响应对象{@link HttpServletResponse} * @param cookie Servlet Cookie对象 */ - public final static void addCookie(HttpServletResponse response, Cookie cookie) { + public static void addCookie(HttpServletResponse response, Cookie cookie) { response.addCookie(cookie); } @@ -415,7 +445,7 @@ public class ServletUtil { * @param name Cookie名 * @param value Cookie值 */ - public final static void addCookie(HttpServletResponse response, String name, String value) { + public static void addCookie(HttpServletResponse response, String name, String value) { response.addCookie(new Cookie(name, value)); } @@ -429,7 +459,7 @@ public class ServletUtil { * @param path Cookie的有效路径 * @param domain the domain name within which this cookie is visible; form is according to RFC 2109 */ - public final static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds, String path, String domain) { + public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds, String path, String domain) { Cookie cookie = new Cookie(name, value); if (domain != null) { cookie.setDomain(domain); @@ -449,7 +479,7 @@ public class ServletUtil { * @param value cookie值 * @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. >0 : Cookie存在的秒数. */ - public final static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) { + public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) { addCookie(response, name, value, maxAgeInSeconds, "/", null); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/analysis/AnalysisEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/analysis/AnalysisEngine.java index 9bdfb478a..90c03c6a3 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/analysis/AnalysisEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/analysis/AnalysisEngine.java @@ -20,7 +20,7 @@ import cn.hutool.extra.tokenizer.TokenizerException; public class AnalysisEngine implements TokenizerEngine { private Analyzer analyzer; - + /** * 构造 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ansj/AnsjEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ansj/AnsjEngine.java index 0942b2316..75d1f3438 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ansj/AnsjEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ansj/AnsjEngine.java @@ -4,8 +4,8 @@ import org.ansj.splitWord.Analysis; import org.ansj.splitWord.analysis.ToAnalysis; import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.tokenizer.TokenizerEngine; import cn.hutool.extra.tokenizer.Result; +import cn.hutool.extra.tokenizer.TokenizerEngine; /** * Ansj分词引擎实现
@@ -38,5 +38,5 @@ public class AnsjEngine implements TokenizerEngine { public Result parse(CharSequence text) { return new AnsjResult(analysis.parseStr(StrUtil.str(text))); } - + } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java index e8ffda26f..1616705d9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java @@ -39,5 +39,5 @@ public class HanLPEngine implements TokenizerEngine { public Result parse(CharSequence text) { return new HanLPResult(this.seg.seg(StrUtil.str(text))); } - + } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java index 9645ff662..7fba710f8 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java @@ -39,5 +39,4 @@ public class IKAnalyzerEngine implements TokenizerEngine { this.seg.reset(StrUtil.getReader(text)); return new IKAnalyzerResult(this.seg); } - } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java b/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java new file mode 100644 index 000000000..ea0f4fe49 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java @@ -0,0 +1,111 @@ +package cn.hutool.http; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.DeflaterInputStream; +import java.util.zip.GZIPInputStream; + +import cn.hutool.core.util.StrUtil; + +/** + * HTTP输入流,此流用于包装Http请求响应内容的流,用于解析各种压缩、分段的响应流内容 + * + * @author Looly + * + */ +public class HttpInputStream extends InputStream { + + /** 原始流 */ + private volatile InputStream in; + + /** + * 构造 + * + * @param response 响应对象 + */ + public HttpInputStream(HttpResponse response) { + init(response); + } + + @Override + public int read() throws IOException { + return this.in.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return this.in.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return this.in.skip(n); + } + + @Override + public int available() throws IOException { + return this.in.available(); + } + + @Override + public void close() throws IOException { + this.in.close(); + } + + @Override + public synchronized void mark(int readlimit) { + this.in.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + this.in.reset(); + } + + @Override + public boolean markSupported() { + return this.in.markSupported(); + } + + /** + * 初始化流 + * + * @param response 响应对象 + */ + private void init(HttpResponse response) { + try { + this.in = (response.status < HttpStatus.HTTP_BAD_REQUEST) ? response.httpConnection.getInputStream() : response.httpConnection.getErrorStream(); + } catch (IOException e) { + if (e instanceof FileNotFoundException) { + // 服务器无返回内容,忽略之 + } else { + throw new HttpException(e); + } + } + + // 在一些情况下,返回的流为null,此时提供状态码说明 + if (null == this.in) { + this.in = new ByteArrayInputStream(StrUtil.format("Error request, response status: {}", response.status).getBytes()); + return; + } + + // TODO 分段响应内容解析 + if(response.isChunked()) { + } + + if (response.isGzip() && false == (response.in instanceof GZIPInputStream)) { + // Accept-Encoding: gzip + try { + this.in = new GZIPInputStream(this.in); + } catch (IOException e) { + // 在类似于Head等方法中无body返回,此时GZIPInputStream构造会出现错误,在此忽略此错误读取普通数据 + // ignore + } + } else if (response.isDeflate() && false == (this.in instanceof DeflaterInputStream)) { + // Accept-Encoding: defalte + this.in = new DeflaterInputStream(this.in); + } + } +} 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 a3671ea63..1e2c7cf4f 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -12,8 +12,6 @@ import java.net.HttpCookie; import java.nio.charset.Charset; import java.util.List; import java.util.Map.Entry; -import java.util.zip.DeflaterInputStream; -import java.util.zip.GZIPInputStream; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FastByteArrayOutputStream; @@ -38,13 +36,13 @@ import cn.hutool.log.StaticLog; public class HttpResponse extends HttpBase implements Closeable { /** 持有连接对象 */ - private HttpConnection httpConnection; + protected HttpConnection httpConnection; /** Http请求原始流 */ - private InputStream in; + protected InputStream in; /** 是否异步,异步下只持有流,否则将在初始化时直接读取body内容 */ private volatile boolean isAsync; /** 响应状态码 */ - private int status; + protected int status; /** 是否忽略读取Http响应体 */ private boolean ignoreBody; /** 从响应中获取的编码 */ @@ -373,9 +371,9 @@ public class HttpResponse extends HttpBase implements Closeable { * @throws HttpException IO异常 */ private HttpResponse init() throws HttpException { + // 获取响应状态码 try { this.status = httpConnection.responseCode(); - this.in = (this.status < HttpStatus.HTTP_BAD_REQUEST) ? this.httpConnection.getInputStream() : this.httpConnection.getErrorStream(); } catch (IOException e) { if (e instanceof FileNotFoundException) { // 服务器无返回内容,忽略之 @@ -394,31 +392,15 @@ public class HttpResponse extends HttpBase implements Closeable { // 存储服务端设置的Cookie信息 GlobalCookieManager.store(httpConnection); + // 获取响应编码 final Charset charset = httpConnection.getCharset(); this.charsetFromResponse = charset; if (null != charset) { this.charset = charset; } - if (null == this.in) { - // 在一些情况下,返回的流为null,此时提供状态码说明 - this.in = new ByteArrayInputStream(StrUtil.format("Error request, response status: {}", this.status).getBytes()); - } else { - // TODO 分段响应内容解析 - - if (isGzip() && false == (in instanceof GZIPInputStream)) { - // Accept-Encoding: gzip - try { - in = new GZIPInputStream(in); - } catch (IOException e) { - // 在类似于Head等方法中无body返回,此时GZIPInputStream构造会出现错误,在此忽略此错误读取普通数据 - // ignore - } - } else if (isDeflate() && false == (in instanceof DeflaterInputStream)) { - // Accept-Encoding: defalte - in = new DeflaterInputStream(in); - } - } + // 获取响应内容流 + this.in = new HttpInputStream(this); // 同步情况下强制同步 return this.isAsync ? this : forceSync(); diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index ac061f1b2..420e9b369 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -63,7 +63,8 @@ public class HttpUtilTest { @Test @Ignore public void getTest5() { - String res = HttpUtil.get("https://comment.bilibili.com/67573272.xml"); + String res = HttpRequest.get("https://comment.bilibili.com/67573272.xml") + .execute().body(); Console.log(res); }