文章目录1.前言2.提取body中的数据3.ServletRequest中的流是一次性的4.可重复读取ServletRequest中的流5.如何对ServletRequest进行s
文章目录
1. 前言
2. 提取 body 中的数据
3. ServletRequest 中的流是一次性的
4. 可重复读取 ServletRequest 中的流
5. 如何对 ServletRequest 进行 setParameter()
6. 总结
1. 前言
ServletRequest
是我们搞 Java Web
经常接触的 Servlet Api
。有些时候我们要经常对其进行一些操作。这里列举一些经常的难点操作。
2. 提取 body 中的数据
前后端交互我们会在 body
中传递数据。我们如何从 body
中提取数据。通常我们会通过 IO
操作:
/**
* obtain request body
*
* @param request the ServletRequest
* @return body string it maybe is null
*/
public static String obtainBody(ServletRequest request) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
try {
br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
} catch (IOException e) {
log.error(" requestBody read error");
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
log.error(" close io error");
}
}
}
return sb.toString();
}
看起来比较凌乱,各种异常处理,IO
开关操作,很不优雅。 如果你使用了 Java 8 你可以这样简化这种操作:
String body = request.getReader().lines().collect(Collectors.joining());
BufferedReader
提供了获取 Java 8 Stream 流的方法 lines()
,我们可以通过以上方法非常方便的获取 ServletRequest
中的 body
3. ServletRequest 中的流是一次性的
不要以为上面的读取 body
操作是完美无瑕的,这里有一个坑。如果按照上面的操作 ServletRequest
中的 body
只能读取一次。 我们传输的数据都是通过流来传输的。ServletRequest
中我们实际上都是通过:
ServletInputStream inputStream = request.getInputStream()
来获取输入流,然后通过 read
系列方法来读取。Java
中的 InputStream
read
方法内部有一个postion
, **它的作用是标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,read
方法会返回 -1
,标志已经读取完了,如果想再次读取,可以调用 reset
方法,position
就会移动到上次调用 mark
的位置,mark
默认是 0
,所以就能从头再读了。
能否 reset
是有条件的,它取决于 markSupported()
,markSupported()
方法返回是否可以进行 mark/reset
。
我们再回头看 ServletInputStream
,其实现并没有重写 reset
方法并不支持 mark/reset
。所以ServletRequest
中的 IO流
只能读取一次 。
4. 可重复读取 ServletRequest 中的流
如果我们使用了个多个 Servlet Filter
进行链式调用并多次操作 ServletRequest
中的流应该怎么做? 我们可以通过 Servlet Api
提供的 javax.servlet.http.HttpServletRequestWrapper
来对其进行包装。 通过继承 HttpServletRequestWrapper
:
public class ReaderRequest extends HttpServletRequestWrapper {
private String body;
public ReaderRequest(HttpServletRequest request) throws IOException {
super(request);
body = request.getReader().lines().collect(Collectors.joining());
}
@Override
public BufferedReader getReader() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
InputStreamReader inputStreamReader = new InputStreamReader(byteArrayInputStream);
return new BufferedReader(inputStreamReader);
}
}
以下是在一个 Servlet Filter
中的标准范例:
public class TestFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 包装
ReaderRequest cachingRequestWrapper=new ReaderRequest((HttpServletRequest) servletRequest);
// 直接从包装读取
String collect = cachingRequestWrapper.getReader().lines().collect(Collectors.joining());
// 传递包装
filterChain.doFilter(cachingRequestWrapper, servletResponse);
}
}
5. 如何对 ServletRequest 进行 setParameter()
从前台传入数据的时候、后台通过 HttpServletRequest
中的 getParameter(String name)
方法对数据进行获取。 如果后台想将数据放进去,下次请求或者其他请求时使用,只能通过setAttribute(String name, Object o)
放入然后从 getAttribute(String name)
获取, 无法通过 getParameter(String name)
获取。我在 Spring Security 实战干货: 玩转自定义登录 就遇到了这个问题
首先说一下getParameter(String name)
是在数据从客户端到服务端之后才有效的,而 则是服务端内部的事情,只有在服务端调用了 setAttribute(String name, Object o)
之后,并且没有重定向(redirect
),在没有到客户端之前 getAttribute(String name)
才有效。
如果希望在服务端中转过程中使用 setParameter() ,我们可以通过 getParameter(String name)
委托给 getAttribute(String name)
来执行。相关实现依然通过 javax.servlet.http.HttpServletRequestWrapper
来实现。
package cn.felord.spring.security.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* @author Felordcn
* @since 2019/10/17 22:09
*/
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
public ParameterRequestWrapper(HttpServletRequest request ) {
super(request);
}
@Override
public String getParameter(String name) {
return (String) super.getAttribute(name);
}
}
你也可借鉴思路实现其它你需要的功能。
6. 总结
今天我们对 ServletRequest
的一些常用的操作进行了讲解。也是我们经常在实际开发中遇到的一些问题。当然你也可以使用一些第三方包来解决这些问题。
关注公众号:Felordcn获取更多资讯
个人博客:https://felord.cn