不同ContentType的请求流读取方式 读取请求流一般就用这三种方式:
request.getInputStream()
request.getReader()
request.getParameterNames()/request.getParameterMap()/request.getParameter(key)
记住,流只能读取一次。
其中,request.getInputStream()和request.getReader()适用:
GET application/x-www-form-urlencoded
POST application/json
不适用:
GET URI参数
POST application/x-www-form-urlencoded
request.getParameter的适用场景刚好相反。
示例代码:request.getInputStream():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RequestMapping ("/is" )@ResponseBody public void is (HttpServletRequest request, HttpServletResponse response) { String result = "" ; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); BufferedInputStream in = new BufferedInputStream(request.getInputStream()); int len = -1 ; byte [] bytes = new byte [1024 * 5 ]; while ((len = in.read(bytes)) != -1 ) { out.write(bytes, 0 , len); } result = out.toString(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isEmpty(result)){ response.setStatus(500 ); return ; } response.setStatus(200 ); }
request.getReader():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RequestMapping ("/reader" )@ResponseBody public void reader (HttpServletRequest request, HttpServletResponse response) { String result = "" ; try { BufferedReader reader = request.getReader(); StringWriter writer = new StringWriter(); char [] chars = new char [256 ]; int count = 0 ; while ((count = reader.read(chars)) > 0 ) { writer.write(chars, 0 , count); } result = writer.toString(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isEmpty(result)){ response.setStatus(500 ); return ; } response.setStatus(200 ); }
request.getParameterMap():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RequestMapping ("/param" )@ResponseBody public void param (HttpServletRequest request, HttpServletResponse response) { String result = "" ; try { Map<String, String[]> parameterMap = request.getParameterMap(); Set<Map.Entry<String, String[]>> entrySet = parameterMap.entrySet(); for (Map.Entry<String, String[]> entry : entrySet) { String key = entry.getKey(); String value = Arrays.toString(entry.getValue()); result += key + "=" + value + "&" ; } System.out.println(result); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isEmpty(result)){ response.setStatus(500 ); return ; } response.setStatus(200 ); }
包装请求流 我们经常要在Filter或Interceptor中提前处理请求,如果在这里读了请求流,会导致后面的Controller读不到任何请求参数。为了解决“流只能读取一次”的问题,我们需要一个HttpServletRequestWrapper。 HttpServletRequestWrapper示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte [] body; private Map<String, String[]> parameterMap = new HashMap<String, String[]>(); private String bodyString; public MyHttpServletRequestWrapper (HttpServletRequest request) { super (request); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); BufferedInputStream in = new BufferedInputStream(request.getInputStream()); int len = -1 ; byte [] bytes = new byte [1024 * 5 ]; while ((len = in.read(bytes)) != -1 ) { out.write(bytes, 0 , len); } body = out.toByteArray(); bodyString = new String(body, StandardCharsets.UTF_8); if (body.length == 0 ) { parameterMap.putAll(request.getParameterMap()); } } catch (IOException e) { e.printStackTrace(); } } @Override public ServletInputStream getInputStream () throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read () throws IOException { return bais.read(); } @Override public boolean isFinished () { return false ; } @Override public boolean isReady () { return false ; } @Override public void setReadListener (ReadListener readListener) { } }; } @Override public BufferedReader getReader () throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public String getParameter (String name) { String[] values = this .parameterMap.get(name); if (values != null & values.length>0 ) { return values[0 ]; } return null ; } @Override public Map<String, String[]> getParameterMap() { return this .parameterMap; } @Override public Enumeration<String> getParameterNames () { Vector<String> vector = new Vector<String>(parameterMap.keySet()); return vector.elements(); } public String getBodyString () { return bodyString; } public void setBodyString (String bodyString) { this .bodyString = bodyString; } }
使用了上面这个包装类的Filter示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Component @WebFilter (urlPatterns = {"/*" }) public class TestFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest); System.out.println("请求URI: " + httpServletRequest.getRequestURI()); System.out.println("请求参数: " + requestWrapper.getBodyString()); chain.doFilter(requestWrapper, httpServletResponse); } @Override public void destroy () { } }
Zuul网关包装请求流 Spring Cloud的组件Zuul网关的一大功能就是可以由我们自定义一串Filter链,在这些Filter中也会需要读请求流。为了在每个Filter以及网关后面的服务能重复读取请求流,是否要在Zuul网关实现一个HttpServletRequestWrapper?Zuul已经帮我们实现好了。
到达网关的请求一定会经过Servlet30WrapperFilter,在这个Filter中,会把请求封装到Servlet30RequestWrapper,它是HttpServletRequestWrapper的实现类。
Servlet30WrapperFilter源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class Servlet30WrapperFilter extends ZuulFilter { private Field requestField = null ; public Servlet30WrapperFilter () { this .requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class , "req", HttpServletRequest.class); Assert.notNull(this .requestField, "HttpServletRequestWrapper.req field not found" ); this .requestField.setAccessible(true ); } protected Field getRequestField () { return this .requestField; } @Override public String filterType () { return PRE_TYPE; } @Override public int filterOrder () { return SERVLET_30_WRAPPER_FILTER_ORDER; } @Override public boolean shouldFilter () { return true ; } @Override public Object run () { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); if (request instanceof HttpServletRequestWrapper) { request = (HttpServletRequest) ReflectionUtils.getField(this .requestField, request); ctx.setRequest(new Servlet30RequestWrapper(request)); } else if (RequestUtils.isDispatcherServletRequest()) { ctx.setRequest(new Servlet30RequestWrapper(request)); } return null ; } }
当我们在自定义Filter中,用下面两行代码获取HTTP请求时,请求的类型就是Servlet30RequestWrapper。
1 2 RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest();
上传文件接口的请求流 WEB项目中经常要实现文件上传,这就涉及到文件流的处理。下面一段代码展示了如何接受文件并保存在本地。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @RequestMapping ("/upload" )public String upload (MultipartFile file, HttpServletRequest request) { String name = request.getParameter("name" ); String type = request.getParameter("type" ); System.out.println("name=" + name); System.out.println("type=" + type); OutputStream os = null ; InputStream is = null ; String fileName = null ; try { is = file.getInputStream(); fileName = file.getOriginalFilename(); String path = "D:\\test\\" ; byte [] bs = new byte [1024 ]; int len; File tempFile = new File(path); if (!tempFile.exists()) { tempFile.mkdirs(); } os = new FileOutputStream(tempFile.getPath() + File.separator + fileName); while ((len = is.read(bs)) != -1 ) { os.write(bs, 0 , len); } } catch (Exception e) { e.printStackTrace(); return "fail" ; } finally { try { if (is != null ) { is.close(); } if (os != null ) { os.close(); } } catch (IOException e) { } } return "success" ; }
用Postman测试该接口时,Body中Content-Type选择form-data。
如何把一个inputStream复制成多份?需要借助ByteArrayInputStream/OutputStream。 示例:
1 2 3 4 5 6 7 8 9 10 11 12 InputStream input = httpconn.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte [] buffer = new byte [1024 ]; int len; while ((len = input.read(buffer)) > -1 ) { baos.write(buffer, 0 , len); } baos.flush(); InputStream stream1 = new ByteArrayInputStream(baos.toByteArray()); InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());
附:流基本方法 输入流读取数据,输出流写入数据 。所以下面的read方法都是InputStream的,write方法都是OutputStream的。
一次读取/写入一个字节 1 2 int read () void write (int b)
读取方法返回值int,代表从input读到的一个字节内容,可以按ASCII码转为char类型。写入方法的参数int则是要写入output的一个字节。尽管int类型数据最多可以是4个字节,但在流这里就被限制在了0~255之间。循环调用这两个读写方法可以达到读取/写入多个字节的效果,但效率低下,不建议使用 。
一次写入多个字节 1 2 void write (byte [] data) void write (byte [] data, int offset, int length)
第一个方法是把字节数组byte[] data的内容全部写入output,第二个方法是指定字节数组从offset开始连续length长度的子数组的内容写入output。数据流向:byte[] -> output PS:如果使用BufferedOutputStream,往流中写数据时,数据会先存放在一个缓冲区,当缓冲区满了,数据才会被发送出去。最好调用output.flush()强制缓冲区发送数据。
一次读取多个字节 1 2 int read (byte [] data) int read (byte [] data, int offset, int length)
第一个方法是读取input填充到字节数组byte[] data,第二个方法是读取input填充到字节数组byte[] data从offset开始连续length长度的子数组。数据流向:input -> byte[]
返回值int代表实际读取的字节数。如下面方法:
1 2 byte [] bytes = new byte [1024 ];int len = input.read(bytes);
上面两行代码尝试从输入流input中读取1024字节,但可能一次读取只读到512字节,因此len=512。 如果希望读取输入流input的全部内容,需要多次调用read方法。例如:
1 2 3 4 5 int len = -1 ;byte [] bytes = new byte [1024 * 5 ];while ((len = input.read(bytes)) != -1 ) { output.write(bytes, 0 , len); }
读不到内容时,len=-1,退出循环,此时输入流in的内容全部读完了。 上面代码表示,从输入流input中读取数据,可能一次全部读完,也可能只读出部分,读出的数据填充到bytes,再把bytes的数据写入输出流output,这个过程循环,直到input数据读完。数据流向:input -> byte[] -> output