Java Web 中对 ServletRequest 的一些很是规操做解决方案

servletrequest.png

1. 前言

ServletRequest 是咱们搞 Java Web 常常接触的 Servlet Api 。有些时候咱们要常常对其进行一些操做。这里列举一些常常的难点操做。html

2. 提取 body 中的数据

先后端交互咱们会在 body 中传递数据。咱们如何从 body 中提取数据。一般咱们会经过 IO 操做:java

/**
      * 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 你能够这样简化这种操做:spring

String body = request.getReader().lines().collect(Collectors.joining());

BufferedReader 提供了获取 Java 8 Stream 流的方法 lines() ,咱们能够经过以上方法很是方便的获取 ServletRequest 中的 body后端

3. ServletRequest 中的流是一次性的

不要觉得上面的读取 body 操做是完美无瑕的,这里有一个坑。若是按照上面的操做 ServletRequest 中的 body 只能读取一次。 咱们传输的数据都是经过流来传输的。ServletRequest 中咱们实际上都是经过:api

ServletInputStream inputStream = request.getInputStream()

来获取输入流,而后经过 read 系列方法来读取。Java 中的 InputStream read 方法内部有一个postion, **它的做用是标志当前流读取到的位置,每读取一次,位置就会移动一次,若是读到最后,read 方法会返回 -1,标志已经读取完了,若是想再次读取,能够调用 reset 方法,position 就会移动到上次调用 mark 的位置,mark 默认是 0,因此就能从头再读了。 可否 reset 是有条件的,它取决于 markSupported(),markSupported() 方法返回是否能够进行 mark/resetapp

咱们再回头看 ServletInputStream ,其实现并无重写 reset 方法并不支持 mark/reset 。因此ServletRequest 中的 IO流 只能读取一次 。ide

4. 可重复读取 ServletRequest 中的流

若是咱们使用了个多个 Servlet Filter 进行链式调用并屡次操做 ServletRequest 中的流应该怎么作? 咱们能够经过 Servlet Api 提供的 javax.servlet.http.HttpServletRequestWrapper 来对其进行包装。 经过继承 HttpServletRequestWrapper :post

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 中的标准范例:ui

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 实战干货: 玩转自定义登陆 就遇到了这个问题3d

首先说一下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

相关文章
相关标签/搜索