溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Request的包裝類(lèi)HttpServletRequestWrapper的用法介紹

發(fā)布時(shí)間:2021-08-24 08:56:03 來(lái)源:億速云 閱讀:165 作者:chen 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹“Request的包裝類(lèi)HttpServletRequestWrapper的用法介紹”,在日常操作中,相信很多人在Request的包裝類(lèi)HttpServletRequestWrapper的用法介紹問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Request的包裝類(lèi)HttpServletRequestWrapper的用法介紹”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

目錄
  • Request的包裝類(lèi)HttpServletRequestWrapper使用

    • 大致的意思是:

    • 上述方案解決了

  • HttpServletRequestWrapper和HttpServletResponseWrapper使用時(shí)的坑

    • WrapperRequest和WrapperResponse的使用

    • 這里涉及到的坑

      • 坑1

      • 坑2

    • 解決問(wèn)題

      • 問(wèn)題延伸

      Request的包裝類(lèi)HttpServletRequestWrapper使用

      在使用zuul進(jìn)行鑒權(quán)的時(shí)候,我們希望從請(qǐng)求Request中獲取輸入流,解析里面的內(nèi)容,奈何InputStream只能被讀取一次。為啥呢?源碼里是這樣說(shuō)的:

      public int read(byte[] b,int off, int len)

         Reads up to len bytes of data into an array of bytes from this input stream. Ifpos equals count, then -1 is returned to indicate end of file. Otherwise, the number k of bytes read is equal to the smaller of len and count-pos.If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner performed by System.arraycopy. The value k is added into pos and k is returned.  

      大致的意思是:

      在InputStream讀取的時(shí)候,會(huì)有一個(gè)pos指針,它指示每次讀取之后下一次要讀取的起始位置。在每次讀取后會(huì)更新pos的值,當(dāng)你下次再來(lái)讀取的時(shí)候是從pos的位置開(kāi)始的,而不是從頭開(kāi)始,所以第二次獲取String中的值的時(shí)候是不全的,API中提供了一個(gè)解決辦法:reset()。但我發(fā)現(xiàn)在inputStream和servlet中根本不起作用。提示 mark/reset not supported 。意思是只有重寫(xiě)過(guò)markSupported()方法的IO流才可以用。所以一般我們使用inputStream,最好在一次內(nèi)處理完所有邏輯。

      那么就沒(méi)法在中途獲取請(qǐng)求流中的數(shù)據(jù)么?當(dāng)然有辦法了,我可是PPZ,只需要重寫(xiě)Request緩存一下流中的數(shù)據(jù)就好了,實(shí)現(xiàn)代碼如下:

      BodyReaderHttpServletRequestWrapper.java

      package com.neo.authUtils;
      import java.io.BufferedReader;
      import java.io.ByteArrayInputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.nio.charset.Charset;
      import java.util.Enumeration;
      import java.util.NoSuchElementException;
      import javax.servlet.ReadListener;
      import javax.servlet.ServletInputStream;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletRequestWrapper;  
      public class BodyReaderHttpServletRequestWrapper extends  
              HttpServletRequestWrapper {  
         // private final byte[] body;  
           -----》private byte[] body;《------- 
          public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {  
              super(request);  
              System.out.println("-------------------打印請(qǐng)求的頭信息------------------------------");    
              Enumeration<?> e = request.getHeaderNames()   ;    
               while(e.hasMoreElements()){    
                   String name = (String) e.nextElement();    
                   String value = request.getHeader(name);    
                  // System.out.println(name+" = "+value);    
               }
               -----》獲取流中的數(shù)據(jù)緩存到字節(jié)數(shù)組中,以后要讀數(shù)據(jù)就用這里的《------
              body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));  
          }
          /**
           * 從請(qǐng)求的頭部獲取用戶的身份識(shí)別id;
           * @param request
           * @return
           */
          public String getJsessionidFromHeader(HttpServletRequest request) {
              String jsessionid = null;//識(shí)別用戶身份的id;
              Enumeration<?> e = request.getHeaderNames()   ;    
              while(e.hasMoreElements()){    
                  String name = (String) e.nextElement();    
                  String value = request.getHeader(name);
                  //cookie = JSESSIONID=B926F6024438D4C693A5E5881595160C; SESSION=458e80dc-e354-4af3-a501-74504a873e70
                  if("cookie".equals(name)) {
                      jsessionid = value.split(";")[0].split("=")[1];
                  }
                  System.out.println(name+"="+value);
              }
             // System.out.println("======jsessionid========>"+jsessionid);
              return jsessionid;
          }
          @Override  
          public BufferedReader getReader() throws IOException {  
              return new BufferedReader(new InputStreamReader(getInputStream()));  
          }  
          @Override  
          public ServletInputStream getInputStream() throws IOException {  
               ------》從緩存的數(shù)據(jù)中讀取數(shù)據(jù)《------
              final ByteArrayInputStream bais = new ByteArrayInputStream(body);  
              return new ServletInputStream() {  
                  public int read() throws IOException {  
                      return bais.read();  
                  }
                  @Override
                  public boolean isFinished() {
                      // TODO Auto-generated method stub
                      return false;
                  }
                  @Override
                  public boolean isReady() {
                      // TODO Auto-generated method stub
                      return false;
                  }
                  @Override
                  public void setReadListener(ReadListener listener) {
                      // TODO Auto-generated method stub
                  }  
              };  
          }  
          @Override  
          public String getHeader(String name) {  
              return super.getHeader(name);  
          }  
          @Override  
          public Enumeration<String> getHeaderNames() {  
              return super.getHeaderNames();  
          }  
         /* @Override  
          public Enumeration<String> getHeaders(String name) {  
              return super.getHeaders(name);  
          }  */
          /**
           * content-type=text/plain;charset=UTF-8
           * 重寫(xiě)getHeaders方法,實(shí)現(xiàn)自定義Content-Type;
           */
          @Override  
          public Enumeration<String> getHeaders(String name) {  
              if ((null != name && name.equals("Content-Type"))||(null != name && name.equals("content-type"))) {  
                  return new Enumeration<String>() {  
                      private boolean hasGetted = false;  
                      @Override  
                      public String nextElement() {  
                          if (hasGetted) {  
                              throw new NoSuchElementException();  
                          } else {  
                              hasGetted = true;
                              return "application/json;charset=utf-8";  
                          }  
                      }  
                      @Override  
                      public boolean hasMoreElements() {  
                          return !hasGetted;  
                      }  
                  };  
              }  
              return super.getHeaders(name);  
          }  
          /**
           * 添加自定義信息到請(qǐng)求體;
           * @param customMsg:自定義的添加到請(qǐng)求體中的信息;
           */
          public void appendCustomMsgToReqBody(String customMsg) {
              String oldBodyString = HttpHelper.getBodyString(this);//oldBodyString一定是通過(guò)當(dāng)前對(duì)象的輸入流解析得來(lái)的,否則接收時(shí)會(huì)報(bào)EOFException;
              String appendMsg = HttpHelper.appendCustomMsgToReqBody(customMsg);
              String requestBodyAfterAppend = appendMsg + "," +oldBodyString;
              //this.body = HttpHelper.appendCustomMsgToReqBody(HttpHelper.appendCustomMsgToReqBody(customMsg)+(HttpHelper.getBodyString(this))).getBytes(Charset.forName("UTF-8"));
              //this.body = HttpHelper.appendCustomMsgToReqBody((HttpHelper.getBodyString(this))).getBytes(Charset.forName("UTF-8"));
              this.body = HttpHelper.appendCustomMsgToReqBody(requestBodyAfterAppend).getBytes(Charset.forName("UTF-8"));
          } 
      }

      HttpHelper.java

      package com.neo.authUtils;
      import java.io.BufferedReader;
      import java.io.ByteArrayInputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.io.UnsupportedEncodingException;
      import java.nio.charset.Charset;
      import javax.servlet.ServletRequest;
      public class HttpHelper {
          /**
           * 獲取post請(qǐng)求中的Body
           *
           * @param request
           * @return
           */
          public static String getBodyString(ServletRequest request) {
              StringBuilder sb = new StringBuilder();
              InputStream inputStream = null;
              BufferedReader reader = null;
              try {
                  inputStream = request.getInputStream();
                  //讀取流并將流寫(xiě)出去,避免數(shù)據(jù)流中斷;
                  reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
                  String line = "";
                  while ((line = reader.readLine()) != null) {
                      sb.append(line);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  if (inputStream != null) {
                      try {
                          inputStream.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  if (reader != null) {
                      try {
                          reader.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
              return sb.toString();
          }
          //添加自定義的信息到請(qǐng)求體中;
          public static String appendCustomMsgToReqBody(String newReqBodyStr) {
              StringBuilder sb = new StringBuilder();
              InputStream inputStream = null;
              BufferedReader reader = null;
              String newReqBody = null;
              try {
                  //通過(guò)字符串構(gòu)造輸入流;
                  inputStream = String2InputStream(newReqBodyStr);
                  reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
                  String line = "";
                  while ((line = reader.readLine()) != null) {
                      sb.append(line);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  if (inputStream != null) {
                      try {
                          inputStream.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  if (reader != null) {
                      try {
                          reader.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
              //返回字符串;
              newReqBody = sb.toString();
              return newReqBody;
          }
          //將字符串轉(zhuǎn)化為輸入流;
          public static InputStream String2InputStream(String str) {
              ByteArrayInputStream stream = null;
              try {
                  stream = new ByteArrayInputStream(str.getBytes("UTF-8"));
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
              }
              return stream;
          }
      }

      上述方案解決了

      使用request.getInpuStream()方法讀取流中的數(shù)據(jù)只能讀取一次的問(wèn)題,其實(shí)當(dāng)我們?cè)谑褂玫谌浇涌跁r(shí),如果請(qǐng)求頭信息和我們的服務(wù)所需不一致,例如第三方接口中頭部信息為:content-type=text/plain;charset=UTF-8

      而我們需要的是:”application/json;charset=utf-8”時(shí),我們也是可以通過(guò)重寫(xiě)對(duì)應(yīng)的方法對(duì)請(qǐng)求的頭部信息進(jìn)行修改的,代碼如下:

      /**
           * content-type=text/plain;charset=UTF-8
           * 重寫(xiě)getHeaders方法,實(shí)現(xiàn)自定義Content-Type;
           */
          @Override  
          public Enumeration<String> getHeaders(String name) {  
              if ((null != name && name.equals("Content-Type"))||(null != name && name.equals("content-type"))) {  
                  return new Enumeration<String>() {  
                      private boolean hasGetted = false;  
                      @Override  
                      public String nextElement() {  
                          if (hasGetted) {  
                              throw new NoSuchElementException();  
                          } else {  
                              hasGetted = true;
                              return "application/json;charset=utf-8";  
                          }  
                      }  
                      @Override  
                      public boolean hasMoreElements() {  
                          return !hasGetted;  
                      }  
                  };  
              }  
              return super.getHeaders(name);  
          }

      當(dāng)我們?cè)诤蠖嗽O(shè)置了頭部信息后,如果不出意外,前端發(fā)送的請(qǐng)求將變?yōu)楹?jiǎn)單請(qǐng)求,這樣,服務(wù)器的處理機(jī)制將簡(jiǎn)單很多。

      HttpServletRequestWrapper和HttpServletResponseWrapper使用時(shí)的坑

      WrapperRequest和WrapperResponse的使用

      在做JavaWeb開(kāi)發(fā)過(guò)程中如果想拿到請(qǐng)求參數(shù)和返回?cái)?shù)據(jù)的話我們就會(huì)使用到這兩個(gè)類(lèi),從類(lèi)名上就可以看出是包裝類(lèi),通過(guò)這兩個(gè)類(lèi)的包裝我們可以使用移花接木的方式獲取到對(duì)應(yīng)的參數(shù)數(shù)據(jù)。

      這里涉及到的坑

      坑1

      如果請(qǐng)求參數(shù)在Body內(nèi)時(shí)取出參數(shù)后,后端程序就無(wú)法再次取出數(shù)據(jù)

      這個(gè)和InputStream不能重復(fù)讀有關(guān) ,這里需要將Request中的數(shù)據(jù)自己保存一份然后在使用的時(shí)候給出新的InputStream,這樣就可以避免使用同一個(gè)InputStream讀取完數(shù)據(jù)后無(wú)法重新讀取數(shù)據(jù)

      @Override
          public ServletInputStream getInputStream() throws IOException {
              //這里每次都重新創(chuàng)建了一個(gè)InputStream
              final ByteArrayInputStream inputStream = new ByteArrayInputStream(bodyData);
              return new ServletInputStream() {
                  @Override
                  public int read() throws IOException {
                      return inputStream.read();
                  }
                  @Override
                  public boolean isFinished() {
                      return false;
                  }
                  @Override
                  public boolean isReady() {
                      return false;
                  }
                  @Override
                  public void setReadListener(ReadListener readListener) {
                  }
              };
          }
      坑2

      使用HttpServletResponseWrapper包裝Response后無(wú)法返回?cái)?shù)據(jù)或者無(wú)法返回html,css等數(shù)據(jù)

      這個(gè)跟網(wǎng)上的教程有關(guān),大多網(wǎng)上的教程是這樣的如下代碼:

      //先定義一個(gè)Filter類(lèi)包裝對(duì)應(yīng)的request和response
      public class WrapperFilter extends OncePerRequestFilter {
          private static Logger logger = LoggerFactory.getLogger(WrapperFilter.class);
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                  logger.debug(" request mapping {} {}", request.getRequestURL(), request.getRequestURI());
                  RequestWrapper requestWrapper = new RequestWrapper(request);
                  ResponseWrapper responseWrapper = new ResponseWrapper(response);
                  filterChain.doFilter(requestWrapper, responseWrapper);
          }
      //response的包裝類(lèi)
      public class ResponseWrapper extends HttpServletResponseWrapper {
          private static Logger logger = LoggerFactory.getLogger(ResponseWrapper.class);
          private final ByteArrayOutputStream buffer;
          private final ServletOutputStream out;
          private final PrintWriter writer;
          
          public ResponseWrapper(HttpServletResponse response) throws IOException {
              super(response);
              buffer = new ByteArrayOutputStream(2048);
              out = new WrapperOutputStream(buffer);
              writer = new PrintWriter(buffer);
          }
          @Override
          public ServletOutputStream getOutputStream() throws IOException {
              return out;
          }
          /**
           *     當(dāng)獲取字符輸出流時(shí),實(shí)際獲取的是我們自己包裝的字符輸出流
           */
          @Override
          public PrintWriter getWriter() {
              return writer;
          }
         /**
           *     獲取返回的數(shù)據(jù)內(nèi)容,這個(gè)是截獲的數(shù)據(jù)
           */
          public byte[] getResponseData() throws IOException {
              flushBuffer();
              return buffer.toByteArray();
          }
          public String getContent() throws IOException {
              flushBuffer();
              return buffer.toString();
          }
      }

      上面的代碼雖然是可以獲取到數(shù)據(jù)的但是,數(shù)據(jù)就無(wú)法返回到前端頁(yè)面了,那么為什么會(huì)出現(xiàn)這樣的問(wèn)題呢,咱們來(lái)分析一下。

      1、包裝類(lèi)對(duì)response進(jìn)行了包裝,并且重寫(xiě)了getWriter和getOutputStream 這樣就可以保證后端使用response向前端寫(xiě)數(shù)據(jù)時(shí)寫(xiě)到我們定義的buffer中

      2、這個(gè)包裝類(lèi)是不范圍的,也就是只在WrapperFilter 之后有效,但是瀏覽器從response讀取數(shù)據(jù)明顯是在WrapperFilter的范圍之外的

      也就是說(shuō)瀏覽器從reponse讀取數(shù)據(jù)時(shí)無(wú)法使用ResponseWrapper而只能使用response 這個(gè)默認(rèn)是ResponseFacade

      3、那么問(wèn)題來(lái)了咱們上面有往response中寫(xiě)入數(shù)據(jù)嗎,顯然是沒(méi)有的也就是寫(xiě)數(shù)據(jù)只在ResponseWrapper中有而ResponseFacade 是沒(méi)有數(shù)據(jù)的所以瀏覽器了就無(wú)法讀取到返回的數(shù)據(jù)啦。

      清楚以上問(wèn)題后問(wèn)題就變得簡(jiǎn)單得多了,那么我們只需要往ResponseFacade 中也定入一份數(shù)據(jù)就可以了

      解決問(wèn)題

      Filter的內(nèi)容不變

      ResponseWrapper中的代碼如下修改

      public class ResponseWrapper extends HttpServletResponseWrapper {
          private static Logger logger = LoggerFactory.getLogger(ResponseWrapper.class);
          private final ByteArrayOutputStream buffer;
          private final ServletOutputStream out;
          private final PrintWriter writer;
         
          public ResponseWrapper(HttpServletResponse response) throws IOException {
              super(response);
              buffer = new ByteArrayOutputStream(2048);
              //這里將response也傳入了WrapperOutputStream 和 WrapperWriter 
              out = new WrapperOutputStream(buffer,  response);
              writer = new WrapperWriter(buffer, response);
          }
          @Override
          public ServletOutputStream getOutputStream() throws IOException {
              return out;
          }
          /**
           *     當(dāng)獲取字符輸出流時(shí),實(shí)際獲取的是我們自己包裝的字符輸出流
           */
          @Override
          public PrintWriter getWriter() {
              return writer;
          }
          public byte[] getResponseData() throws IOException {
              flushBuffer();
              return buffer.toByteArray();
          }
          public String getContent() throws IOException {
              flushBuffer();
              return buffer.toString();
          }
      }

      這里將response也傳入了WrapperOutputStream 和 WrapperWriter 這樣我們?cè)谧鰯?shù)據(jù)寫(xiě)入的時(shí)候就可以同時(shí)向reponse中寫(xiě)入數(shù)據(jù)了

      這兩個(gè)類(lèi)的實(shí)現(xiàn)如下:

      public class WrapperOutputStream extends ServletOutputStream {
          private OutputStream innerOut;
          private HttpServletResponse response;
          public WrapperOutputStream(OutputStream innerOut, HttpServletResponse response) {
              super();
              this.response = response;
              this.innerOut = innerOut;
          }
          @Override
          public boolean isReady() {
              if(response == null){
                  return false;
              }
              try {
                  return response.getOutputStream().isReady();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return false;
          }
          @Override
          public void setWriteListener(WriteListener listener) {
              if(response != null){
                  try {
                      ((CoyoteOutputStream)response.getOutputStream()).setWriteListener(listener);
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          //關(guān)鍵在這里
          @Override
          public void write(int b) throws IOException {
              if(response != null){
                  response.getOutputStream().write(b);
              }
              innerOut.write(b);
          }
      }
      //另一個(gè)類(lèi)
      public class WrapperWriter extends PrintWriter {
          private HttpServletResponse response;
          ByteArrayOutputStream output;
          public WrapperWriter(ByteArrayOutputStream out, HttpServletResponse response) {
              super(out);
              this.response = response;
              this.output = out;
          }
          //關(guān)鍵在這里
          @Override
          public void write(int b){
              super.write(b);
              try {
                  response.getWriter().write(b);
              } catch (IOException e) {
                  e.printStackTrace();
                  this.setError();
              }
          }
          //關(guān)鍵在這里
          @Override
          public void write(String s, int off, int len) {
              super.write(s,off,len);
              try {
                  response.getWriter().write(s,off,len);
              } catch (IOException e) {
                  e.printStackTrace();
                  this.setError();
              }
          }
      }

      以上可以看到數(shù)據(jù)的寫(xiě)入變成了寫(xiě)兩份一份寫(xiě)到了自定義的對(duì)象中一份寫(xiě)到了response中這樣返回到前端的responnse就不會(huì)沒(méi)有數(shù)據(jù)了

      到此問(wèn)題完全解決,這里還需要注意的就是PrintWriter 有多個(gè)writer重載需要都進(jìn)行重寫(xiě)才行

      問(wèn)題延伸

      有人會(huì)問(wèn)能不能直接將response中的OutputStream和Writer獲取到分配給對(duì)應(yīng)的WrapperOutputStream 和WrapperWriter而不是直接傳入response本身,答案是不可以的,response是不能同時(shí)獲取OutputStream和Writer的因?yàn)樗麄儾僮鞯氖峭粋€(gè)數(shù)據(jù),所以ResponseFacade 實(shí)現(xiàn)時(shí)對(duì)其進(jìn)行了保護(hù)——同時(shí)只能獲取OutputStream和Writer中的一個(gè)。

      到此,關(guān)于“Request的包裝類(lèi)HttpServletRequestWrapper的用法介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

      向AI問(wèn)一下細(xì)節(jié)

      免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

      AI