" />

最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

解決HttpServletResponse和HttpServletRequest取值的2個(gè)坑

 更新時(shí)間:2023年12月28日 09:46:08   作者:你的豆腐在這  
這篇文章主要介紹了解決HttpServletResponse和HttpServletRequest取值的2個(gè)坑問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

有時(shí)候,我們需要用攔截器對(duì)Request或者Response流里面的數(shù)據(jù)進(jìn)行攔截,讀取里面的一些信息,也許是作為日志檢索,也許是做一些校驗(yàn),但是當(dāng)我們讀取里請(qǐng)求或者回調(diào)的流數(shù)據(jù)后,會(huì)發(fā)現(xiàn)這些流數(shù)據(jù)在下游就無法再次被消費(fèi)了,這里面是其實(shí)存在著兩個(gè)潛在的坑。

坑一

Request的 getInputStream()、getReader()、getParameter()方法互斥,也就是使用了其中一個(gè),再使用另外的兩,是獲取不到數(shù)據(jù)的。

除了互斥外,getInputStream()和getReader()都只能使用一次,getParameter單線程上可重復(fù)使用。

三個(gè)方法互斥原因

org.apache.catalina.connector.Request方法實(shí)現(xiàn)了javax.servlet.http.HttpServletRequest接口,我們來看看這三個(gè)方法的實(shí)現(xiàn):

getInputStream

@Override
public ServletInputStream getInputStream() throws IOException {
    if (usingReader) {
        throw new IllegalStateException
            (sm.getString("coyoteRequest.getInputStream.ise"));
    }
    usingInputStream = true;
    if (inputStream == null) {
        inputStream = new CoyoteInputStream(inputBuffer);
    }
    return inputStream;
}

getReader

@Override
public BufferedReader getReader() throws IOException {
    if (usingInputStream) {
        throw new IllegalStateException
            (sm.getString("coyoteRequest.getReader.ise"));
    }
    usingReader = true;
    inputBuffer.checkConverter();
    if (reader == null) {
        reader = new CoyoteReader(inputBuffer);
    }
    return reader;
}

首先來看getInputStream()和getReader()這兩個(gè)方法,可以看到,在讀流時(shí)分別用usingReader和usingInputStream標(biāo)志做了限制,這兩個(gè)方法的互斥很好理解。

下面看一看getParameter()方法是怎么跟他們互斥的。

getParameter

@Override
public String getParameter(String name) {
	// 只會(huì)解析一遍Parameter
    if (!parametersParsed) {
        parseParameters();
    }
  	// 從coyoteRequest中獲取參數(shù)
    return coyoteRequest.getParameters().getParameter(name);
}

粗略一看好像沒有互斥,別著急,繼續(xù)往下看,我們進(jìn)到parseParameters()方法中來看一看(可以直接看源碼中間部分):

protected void parseParameters() {
	//標(biāo)識(shí)位,標(biāo)志已經(jīng)被解析過。
    parametersParsed = true;
    Parameters parameters = coyoteRequest.getParameters();
    boolean success = false;
    try {
        // Set this every time in case limit has been changed via JMX
        parameters.setLimit(getConnector().getMaxParameterCount());
        // getCharacterEncoding() may have been overridden to search for
        // hidden form field containing request encoding
        String enc = getCharacterEncoding();
        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
        if (enc != null) {
            parameters.setEncoding(enc);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringEncoding(enc);
            }
        } else {
            parameters.setEncoding
                (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringEncoding
                    (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
            }
        }
        parameters.handleQueryParameters();
		// 重點(diǎn)看這里:這里會(huì)判斷是否有讀取過流。如果有,則直接return。
        if (usingInputStream || usingReader) {
            success = true;
            return;
        }
        if( !getConnector().isParseBodyMethod(getMethod()) ) {
            success = true;
            return;
        }
        String contentType = getContentType();
        if (contentType == null) {
            contentType = "";
        }
        int semicolon = contentType.indexOf(';');
        if (semicolon >= 0) {
            contentType = contentType.substring(0, semicolon).trim();
        } else {
            contentType = contentType.trim();
        }
        if ("multipart/form-data".equals(contentType)) {
            parseParts(false);
            success = true;
            return;
        }
        if (!("application/x-www-form-urlencoded".equals(contentType))) {
            success = true;
            return;
        }
        int len = getContentLength();
        if (len > 0) {
            int maxPostSize = connector.getMaxPostSize();
            if ((maxPostSize > 0) && (len > maxPostSize)) {
                Context context = getContext();
                if (context != null && context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.postTooLarge"));
                }
                checkSwallowInput();
                return;
            }
            byte[] formData = null;
            if (len < CACHED_POST_LEN) {
                if (postData == null) {
                    postData = new byte[CACHED_POST_LEN];
                }
                formData = postData;
            } else {
                formData = new byte[len];
            }
            try {
                if (readPostBody(formData, len) != len) {
                    return;
                }
            } catch (IOException e) {
                // Client disconnect
                Context context = getContext();
                if (context != null && context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.parseParameters"),
                            e);
                }
                return;
            }
            parameters.processParameters(formData, 0, len);
        } else if ("chunked".equalsIgnoreCase(
                coyoteRequest.getHeader("transfer-encoding"))) {
            byte[] formData = null;
            try {
                formData = readChunkedPostBody();
            } catch (IOException e) {
                // Client disconnect or chunkedPostTooLarge error
                Context context = getContext();
                if (context != null && context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.parseParameters"),
                            e);
                }
                return;
            }
            if (formData != null) {
                parameters.processParameters(formData, 0, formData.length);
            }
        }
        success = true;
    } finally {
        if (!success) {
            parameters.setParseFailed(true);
        }
    }
}

這樣一來,就說明了getParameter()方法也不能隨意讀取的。那么為什么它們都只能讀取一次呢?

只能讀取一次的原因

getInputStream()和getReader()方法都只能讀取一次,而getParameter()是在單線程上可重復(fù)使用,主要是因?yàn)間etParameter()中會(huì)解析流中的數(shù)據(jù)后存放在了一個(gè)LinkedHashMap中,相關(guān)的內(nèi)容可以看Parameters類中的封裝,在上面parseParameters()方法的源碼中也可以看到一開始就生成了一個(gè)Parameters對(duì)象。

后續(xù)讀取的數(shù)據(jù)都存在了這個(gè)對(duì)象中。但是getInputStream()和getReader()方法就沒有這樣做,getInputStream()方法返回CoyoteInputStream,getReader()返回CoyoteReader,CoyoteInputStream繼承了InputStream,CoyoteReader繼承了BufferedReader,從源碼看InputStream和BufferedReader在讀取數(shù)據(jù)后,記錄數(shù)據(jù)讀取的坐標(biāo)不會(huì)被重置,因?yàn)镃oyoteInputStream和CoyoteReader都沒有實(shí)現(xiàn)reset方法,這導(dǎo)致數(shù)據(jù)只能被讀取一次。

坑二

Response與Request一樣,getOutputStream()和getWriter()方法也是互斥的,并且Response中的body數(shù)據(jù)也只能消費(fèi)一次。

互斥原因

getOutputStream

@Override
public ServletOutputStream getOutputStream()
    throws IOException {
    if (usingWriter) {
        throw new IllegalStateException
            (sm.getString("coyoteResponse.getOutputStream.ise"));
    }
    usingOutputStream = true;
    if (outputStream == null) {
        outputStream = new CoyoteOutputStream(outputBuffer);
    }
    return outputStream;
}

getWriter

@Override
public PrintWriter getWriter() throws IOException {
    if (usingOutputStream) {
        throw new IllegalStateException
            (sm.getString("coyoteResponse.getWriter.ise"));
    }
    if (ENFORCE_ENCODING_IN_GET_WRITER) {
        setCharacterEncoding(getCharacterEncoding());
    }
    usingWriter = true;
    outputBuffer.checkConverter();
    if (writer == null) {
        writer = new CoyoteWriter(outputBuffer);
    }
    return writer;
}

只能讀取一次的原因

在Response中,讀取是指從OutputStream中重新把body數(shù)據(jù)讀出來,而OutputStream也和InputStream存在同樣的問題,流只能讀取一次,這里就不展開講了。

解決方案

在Spring庫中,提供了ContentCachingResponseWrapper和ContentCachingRequestWrapper兩個(gè)類,分別解決了Response和Request不能重復(fù)讀以及方法互斥問題。

我們可以直接用ContentCachingRequestWrapper來包裝Request,ContentCachingResponseWrapper來包裝Response,包裝后,在讀取流數(shù)據(jù)的時(shí)候會(huì)將這個(gè)數(shù)據(jù)緩存一份,等讀完以后,再將流數(shù)據(jù)重新寫入Request或者Response就可以了。

下面是一個(gè)簡(jiǎn)單的使用示例:

ContentCachingResponseWrapper responseToCache = new ContentCachingResponseWrapper(response);
String responseBody = new String(responseToCache.getContentAsByteArray());
responseToCache.copyBodyToResponse();

緩存一份流數(shù)據(jù),這就是基本的解決思路,下面我們從源碼層面來看一看,主要關(guān)注getContentAsByteArray()、copyBodyToResponse()方法就行:

public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
   private final FastByteArrayOutputStream content = new FastByteArrayOutputStream(1024);
   private final ServletOutputStream outputStream = new ResponseServletOutputStream();
   private PrintWriter writer;
   private int statusCode = HttpServletResponse.SC_OK;
   private Integer contentLength;
   /**
    * Create a new ContentCachingResponseWrapper for the given servlet response.
    * @param response the original servlet response
    */
   public ContentCachingResponseWrapper(HttpServletResponse response) {
      super(response);
   }
   @Override
   public void setStatus(int sc) {
      super.setStatus(sc);
      this.statusCode = sc;
   }
   @SuppressWarnings("deprecation")
   @Override
   public void setStatus(int sc, String sm) {
      super.setStatus(sc, sm);
      this.statusCode = sc;
   }
   @Override
   public void sendError(int sc) throws IOException {
      copyBodyToResponse(false);
      try {
         super.sendError(sc);
      }
      catch (IllegalStateException ex) {
         // Possibly on Tomcat when called too late: fall back to silent setStatus
         super.setStatus(sc);
      }
      this.statusCode = sc;
   }
   @Override
   @SuppressWarnings("deprecation")
   public void sendError(int sc, String msg) throws IOException {
      copyBodyToResponse(false);
      try {
         super.sendError(sc, msg);
      }
      catch (IllegalStateException ex) {
         // Possibly on Tomcat when called too late: fall back to silent setStatus
         super.setStatus(sc, msg);
      }
      this.statusCode = sc;
   }
   @Override
   public void sendRedirect(String location) throws IOException {
      copyBodyToResponse(false);
      super.sendRedirect(location);
   }
   @Override
   public ServletOutputStream getOutputStream() throws IOException {
      return this.outputStream;
   }
   @Override
   public PrintWriter getWriter() throws IOException {
      if (this.writer == null) {
         String characterEncoding = getCharacterEncoding();
         this.writer = (characterEncoding != null ? new ResponsePrintWriter(characterEncoding) :
               new ResponsePrintWriter(WebUtils.DEFAULT_CHARACTER_ENCODING));
      }
      return this.writer;
   }
   @Override
   public void flushBuffer() throws IOException {
      // do not flush the underlying response as the content as not been copied to it yet
   }
   @Override
   public void setContentLength(int len) {
      if (len > this.content.size()) {
         this.content.resize(len);
      }
      this.contentLength = len;
   }
   // Overrides Servlet 3.1 setContentLengthLong(long) at runtime
   public void setContentLengthLong(long len) {
      if (len > Integer.MAX_VALUE) {
         throw new IllegalArgumentException("Content-Length exceeds ContentCachingResponseWrapper's maximum (" +
               Integer.MAX_VALUE + "): " + len);
      }
      int lenInt = (int) len;
      if (lenInt > this.content.size()) {
         this.content.resize(lenInt);
      }
      this.contentLength = lenInt;
   }
   @Override
   public void setBufferSize(int size) {
      if (size > this.content.size()) {
         this.content.resize(size);
      }
   }
   @Override
   public void resetBuffer() {
      this.content.reset();
   }
   @Override
   public void reset() {
      super.reset();
      this.content.reset();
   }
   /**
    * Return the status code as specified on the response.
    */
   public int getStatusCode() {
      return this.statusCode;
   }
   /**
    * Return the cached response content as a byte array.
    */
   public byte[] getContentAsByteArray() {
      return this.content.toByteArray();
   }
   /**
    * Return an {@link InputStream} to the cached content.
    * @since 4.2
    */
   public InputStream getContentInputStream() {
      return this.content.getInputStream();
   }
   /**
    * Return the current size of the cached content.
    * @since 4.2
    */
   public int getContentSize() {
      return this.content.size();
   }
   /**
    * Copy the complete cached body content to the response.
    * @since 4.2
    */
   public void copyBodyToResponse() throws IOException {
      copyBodyToResponse(true);
   }
   /**
    * Copy the cached body content to the response.
    * @param complete whether to set a corresponding content length
    * for the complete cached body content
    * @since 4.2
    */
   protected void copyBodyToResponse(boolean complete) throws IOException {
      if (this.content.size() > 0) {
         HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
         if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) {
            rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
            this.contentLength = null;
         }
         this.content.writeTo(rawResponse.getOutputStream());
         this.content.reset();
         if (complete) {
            super.flushBuffer();
         }
      }
   }
   private class ResponseServletOutputStream extends ServletOutputStream {
      @Override
      public void write(int b) throws IOException {
         content.write(b);
      }
      @Override
      public void write(byte[] b, int off, int len) throws IOException {
         content.write(b, off, len);
      }
   }
   private class ResponsePrintWriter extends PrintWriter {
      public ResponsePrintWriter(String characterEncoding) throws UnsupportedEncodingException {
         super(new OutputStreamWriter(content, characterEncoding));
      }
      @Override
      public void write(char buf[], int off, int len) {
         super.write(buf, off, len);
         super.flush();
      }
      @Override
      public void write(String s, int off, int len) {
         super.write(s, off, len);
         super.flush();
      }
      @Override
      public void write(int c) {
         super.write(c);
         super.flush();
      }
   }
}

而ContentCachingRequestWrapper的解決思路也是差不多。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 關(guān)于mybatis resulttype 返回值異常的問題

    關(guān)于mybatis resulttype 返回值異常的問題

    這篇文章主要介紹了mybatis resulttype 返回值異常的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 提示:Decompiled.class file,bytecode version如何解決

    提示:Decompiled.class file,bytecode version如何解決

    在處理Decompiled.classfile和bytecodeversion問題時(shí),通過修改Maven配置文件,添加阿里云鏡像并去掉默認(rèn)鏡像,解決了下載源的問題,同時(shí),檢查并修改了依賴版本,確保了問題的解決
    2024-12-12
  • 深入了解Java包與訪問控制權(quán)限

    深入了解Java包與訪問控制權(quán)限

    這篇文章主要帶你掌握J(rèn)ava中包的定義及使用以及Java中的4種訪問權(quán)限,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-09-09
  • Java SpringMVC實(shí)現(xiàn)國際化整合案例分析(i18n)

    Java SpringMVC實(shí)現(xiàn)國際化整合案例分析(i18n)

    本篇文章主要介紹了Java SpringMVC實(shí)現(xiàn)國際化整合案例分析(i18n),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Java中的遞歸方法示例介紹

    Java中的遞歸方法示例介紹

    大家好,本篇文章主要講的是Java中的遞歸方法示例介紹,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • Java實(shí)現(xiàn)的生成二維碼和解析二維碼URL操作示例

    Java實(shí)現(xiàn)的生成二維碼和解析二維碼URL操作示例

    這篇文章主要介紹了Java實(shí)現(xiàn)的生成二維碼和解析二維碼URL操作,結(jié)合實(shí)例形式分析了Java創(chuàng)建與解析二維碼,以及文件讀寫等相關(guān)操作技巧,需要的朋友可以參考下
    2018-07-07
  • springboot的切面應(yīng)用方式(注解Aspect)

    springboot的切面應(yīng)用方式(注解Aspect)

    文章總結(jié):Spring?Boot提供了三種攔截器:Filter、Interceptor和Aspect,Filter主要用于內(nèi)容過濾和非登錄狀態(tài)的非法請(qǐng)求過濾,無法獲取Spring框架相關(guān)的信息,Interceptor可以在獲取請(qǐng)求類名、方法名的同時(shí),獲取請(qǐng)求參數(shù),但無法獲取參數(shù)值
    2024-11-11
  • SpringCloud?Feign使用ApacheHttpClient代替默認(rèn)client方式

    SpringCloud?Feign使用ApacheHttpClient代替默認(rèn)client方式

    這篇文章主要介紹了SpringCloud?Feign使用ApacheHttpClient代替默認(rèn)client方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java?Stream流的常見生成和操作方法總結(jié)

    Java?Stream流的常見生成和操作方法總結(jié)

    從Java1.8開始提出了Stream流的概念,本文將通過示例為大家詳細(xì)講解一下Stream流的常見生成和操作方法,感興趣的小伙伴可以了解一下
    2022-09-09
  • 簡(jiǎn)單實(shí)現(xiàn)Java驗(yàn)證碼功能

    簡(jiǎn)單實(shí)現(xiàn)Java驗(yàn)證碼功能

    這篇文章主要為大家詳細(xì)介紹了簡(jiǎn)單實(shí)現(xiàn)Java驗(yàn)證碼功能的代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05

最新評(píng)論

太仆寺旗| 丹巴县| 双鸭山市| 宜兰市| 永胜县| 千阳县| 敦化市| 晋城| 大同县| 西藏| 谷城县| 东乌珠穆沁旗| 广水市| 游戏| 湛江市| 札达县| 东莞市| 塘沽区| 繁昌县| 延长县| 双辽市| 广德县| 茌平县| 饶平县| 龙口市| 南昌市| 黄浦区| 景宁| 武清区| 蒲城县| 宾川县| 玛曲县| 图木舒克市| 庄浪县| 怀安县| 海淀区| 平谷区| 个旧市| 正蓝旗| 顺平县| 安陆市|