不同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