1. 程式人生 > >Servlet-Reader、InputStream

Servlet-Reader、InputStream

之前 [] cnblogs encoding repr row cal query method

先來看javax.servlet.ServletRequest中getInputStreamgetReader以及getParameter的註釋說明

 1 /**
 2  * Retrieves the body of the request as binary data using
 3  * a {@link ServletInputStream}.  Either this method or 
 4  * {@link #getReader} may be called to read the body, not both.
 5  *
 6  * @return a {@link ServletInputStream} object containing
7 * the body of the request 8 * 9 * @exception IllegalStateException if the [email protected] #getReader} method 10 * has already been called for this request 11 * 12 * @exception IOException if an input or output exception occurred 13 */ 14 public ServletInputStream getInputStream() throws
IOException;

 1 /**
 2  * Retrieves the body of the request as character data using
 3  * a <code>BufferedReader</code>.  The reader translates the character
 4  * data according to the character encoding used on the body.
 5  * Either this method or {@link #getInputStream} may be called to read the
6 * body, not both. 7 * 8 * @return a <code>BufferedReader</code> containing the body of the request 9 * 10 * @exception UnsupportedEncodingException if the character set encoding 11 * used is not supported and the text cannot be decoded 12 * 13 * @exception IllegalStateException if [email protected] #getInputStream} method 14 * has been called on this request 15 * 16 * @exception IOException if an input or output exception occurred 17 * 18 * @see #getInputStream 19 */ 20 public BufferedReader getReader() throws IOException;

 1     /**
 2      * Returns the value of a request parameter as a <code>String</code>,
 3      * or <code>null</code> if the parameter does not exist. Request parameters
 4      * are extra information sent with the request.  For HTTP servlets,
 5      * parameters are contained in the query string or posted form data.
 6      *
 7      * <p>You should only use this method when you are sure the
 8      * parameter has only one value. If the parameter might have
 9      * more than one value, use {@link #getParameterValues}.
10      *
11      * <p>If you use this method with a multivalued
12      * parameter, the value returned is equal to the first value
13      * in the array returned by <code>getParameterValues</code>.
14      *
15      * <p>If the parameter data was sent in the request body, such as occurs
16      * with an HTTP POST request, then reading the body directly via {@link
17      * #getInputStream} or {@link #getReader} can interfere
18      * with the execution of this method.
19      *
20      * @param name a <code>String</code> specifying the name of the parameter
21      *
22      * @return a <code>String</code> representing the single value of
23      * the parameter
24      *
25      * @see #getParameterValues
26      */
27     public String getParameter(String name);

通過註釋我們可以知道,getInputStream和getReader只能調用其一,如果已經調用了一個,再去調用另一個時就會拋IllegalStateException(同一個可以被多次調用)。當body中存有參數數據時,通過getInputStream、getReader讀取數據,getParameter會被影響。

tomcat對於getInputStreamgetReader的處理

tomcat通過兩個using標識量來實現getInputStream、getReader的互斥訪問(並沒有線程安全的相關處理),二者只能調用其一,只要不被關閉輸入流,就可以多次調用(即便是多次調用,其實返回的也是同一個對象,只說單線程),但一旦關閉就不可以再從輸入流中讀取數據了(使用的應該是shutdownInputStream)。

關閉輸入後,socket處於半關閉狀態(使用的應該是shutdownInputStream),此後仍然可以向client寫數據。

註意,getInputStream和getReader針對的是body數據,並不會涉及請求行和頭信息,也是因為這樣,通常我們會將token信息放在header中,在filter處理token後就不會影響到之後的流處理了。

 1 public ServletInputStream getInputStream() throws IOException {
 2     //getReader類似
 3   if (usingReader) {
 4     throw new IllegalStateException
 5         (sm.getString("coyoteRequest.getInputStream.ise"));
 6   }
 7     //getReader類似
 8   usingInputStream = true;
 9   if (inputStream == null) {
10       //getReader是new CoyoteReader(inputBuffer)
11       //getParameter時調用的是getStream,只是比getInputStream少了using的相關處理,
12       //同樣是inputStream = new CoyoteInputStream(inputBuffer)
13     inputStream = new CoyoteInputStream(inputBuffer);
14   }
15   return inputStream;
16 }

tomcat對於getParameter的處理

可以看到getParameter只會處理multipart/form-data、application/x-www-form-urlencoded兩種Content-Type,後者就是普通的form,可以通過getParameter(String name)得到對應的value。如果是前者(多部),getParameter(String name)是得不到value的,但可以通過getPart(String name)得到Part,再進一步處理。tomcat對於兩種Content-Type數據的存儲是分開的,multipart/form-data存在了Collection<Part> parts,application/x-www-form-urlencoded存在了ParameterMap<String, String[]> parameterMap,getParameter時雖然會填充parts,但並不會從parts獲取元素(當然,調用getPart也會解析parts,但不會解析parameters)。

註意,如果是post請求,getParameter會涉及body數據。在tomcat的實現中,getParameter會先處理url中的查詢參數,然後會檢查getInputStream或getReader是否被調用,如果被調用過則返回,如果未被調用,則會完全讀取body數據(並沒有using標記),此後如果再調用getInputStream或getReader處理body,就沒有數據可讀了(返回-1)。也就是說getParameter、getInputStream和getReader只能有效調用其一。

 1 //如果調用過getInputStream或getReader,則不對再body處理。
 2 //之前已經對url上的查詢參數做了處理,getParameter可以無障礙訪問url上的查詢參數,
 3 //之後的邏輯僅針對body
 4 if (usingInputStream || usingReader) {
 5   success = true;
 6   return;
 7 }
 8 
 9 if( !getConnector().isParseBodyMethod(getMethod()) ) {
10   success = true;
11   return;
12 }
13 
14 String contentType = getContentType();
15 if (contentType == null) {
16   contentType = "";
17 }
18 int semicolon = contentType.indexOf(‘;‘);
19 if (semicolon >= 0) {
20   contentType = contentType.substring(0, semicolon).trim();
21 } else {
22   contentType = contentType.trim();
23 }
24 //getParameter方法會處理multipart/form-data、application/x-www-form-urlencoded兩種Content-Type(都是form),
25 //但getParameter只能獲取到application/x-www-form-urlencoded的數據
26 if ("multipart/form-data".equals(contentType)) {
27     //多部會被放到parts容器中
28   parseParts(false);
29   success = true;
30   return;
31 }
32 if (!("application/x-www-form-urlencoded".equals(contentType))) {
33   success = true;
34   return;
35 }
36 //對application/x-www-form-urlencoded數據進一步處理,數據會存放在parameterMap中

如果我們在filter中對body進行了處理(比如將token存在了body中,需要在filter中處理token),那麽在之後的流程中就無法再對body處理了,這該怎麽辦?
springmvc提供了一個輔助類可以解決類似問題,即org.springframework.web.util.ContentCachingRequestWrapper,其大致原理就是將body數據放在緩存中,以便後續訪問。

Servlet-Reader、InputStream