Filter 处理 request 和 response
背景:在 Filter 根据请求参数,判断用户登陆是否有效。后来是想就请求参数和返回值之间的关系做一个缓存。至于为什么不用 Spring … 因为公司不用。所以项目都是 Servlet + Filter …
-
session:
关于这个,有必要提一下:所谓的 session 失效是在一定时间内没有 session 活动,然后 session 失效。浏览器的 Cookie 中 JSESSIONID 就是保存当前会话的 sessionId, 每一次浏览器请求服务器时,服务器根据 JSESSIONID 中的值来查找当前有效的 session,若存在则把上下文切换到该 session,否则创建一个新的 session 并生产一个新的 sessionId。 -
登陆 PC
PC 成功登陆的时候记录当前的账户 Id 和 sessionId,sessionId 为 key;并会根据账户 Id 清除此前已经记录的 sessionId 。在 Filter 中根据当前的 sessionId 去查找记录的账户 Id,如果找不到或者找到的与当前发起请求的 账户 Id 不匹配,则判断用户需要登陆。(虽然有点儿 low 但貌似也保证了只能在一个浏览器登陆) -
移动终端
思路具是一样的,在成功登陆之后会把当前的 sessionId 返回给移动端。然后每次请求的时候把 获取到的 sessionId 加入 header 中,并把获取到的 sessionId 加入传入参数中(个人觉得没必要加入参数中,服务端可以通过读取 cookie 来获取移动端传入的 sessionId)。然后通过比较 参数中的 sessionId 和 服务器中的 sessionId 来判断是否登陆超时。 -
缓存
这个预想是根据 request 中的参数和请求的方法,来缓存一部分数据。这样就没必要每次去查数据库了。
问题
- 毫无疑问是流,因为流在被读了一次之后第二次就不能读了
-
request:
需要包装一下:下面的类,把流信息读取后存入 byte 数组,然后重写了getInputStream
方法,当获取流的时候就读取 byte 数组的数据。// 略 BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(hrequest); // 略
然后通过
chain.doFilter(requestWrapper, response);
调用就不会存在重复读流的问题。// BodyReaderHttpServletRequestWrapper.java public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{ private byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); InputStream is = request.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buff[] = new byte[ 1024 ]; int read; while( ( read = is.read( buff ) ) > 0 ) { baos.write( buff, 0, read ); } this.body = baos.toByteArray(); } public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { return new BufferedServletInputStream(this.body); } }
BodyReaderHttpServletRequestWrapper 这个代码是网上找的。
-
response
也是需要包装一下:因为如果在chain.doFilter(requestWrapper, responseWrapper);
之后读取了 response 中的数据进行缓存,那客户端就不能读取数据。 所以和 request 一样要缓存,其实这里就是把数据读出来,然后再处理后再写会流中。参考代码如下:public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String reqUrl = req.getServletPath().trim(); if (reqUrl.indexOf("doAction") != -1 || reqUrl.indexOf("doPost") != -1) { BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(req); // 包装响应对象 resp 并缓存响应数据 ResponseWrapper responseWrapper = new ResponseWrapper(resp); chain.doFilter(requestWrapper, responseWrapper); byte[] bytes = responseWrapper.getBytes(); // 获取缓存的响应数据 ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gzipOut = new GZIPOutputStream(bout); // 创建 gzipOut.write(bytes); // 将响应的数据写到 Gzip 压缩流中 gzipOut.close(); // 将数据刷新到 bout 字节流数组 byte[] bts = bout.toByteArray(); resp.setHeader("Content-Encoding", "gzip"); // 设置响应头信息 resp.getOutputStream().write(bts); // 将压缩数据响应给客户端 } else { chain.doFilter(request, response); } }
惭愧,这段代码也是参考别人的。时间太久不记得了……
-
写数据的问题
有时候需要提前返回结果,就需要往 response 中写一定的数据给客户端做响应。这里遇到的问题是响应头设置不对,客户端拿不到数据。最后的代码如下:public static void writeJSONToClientJson(ServletResponse response, String state, String msg) { try { HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper((HttpServletResponse) response); JSONObject obj = new JSONObject(); obj.put("STATE", state); obj.put("MSG", msg); responseWrapper.setCharacterEncoding("UTF-8"); responseWrapper.setContentType("text/html;charset=utf-8"); PrintWriter out = responseWrapper.getWriter(); out.print(obj.toString()); out.flush(); out.close(); } catch (Exception e) { Log.log(e); } }
-
Base64 解码问题
前端传回来的数据是 Base64 编码的,解码的时候就发现使用在线解码工具是能正常解码的,但是用 JDK 的解码出来就数据就不全。最后 使用的是org.apache.axiom.util.base64.Base64Utils.decode(String)
这个方法。 这个很伤,试了好多都不能正常解码出来……