• Servlet-Reader、InputStream


    先来看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 {@link #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 {@link #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数据放在ByteArrayOutputStream中,后续可以通过getContentAsByteArray访问字节序列。

  • 相关阅读:
    英文字母打字速度测试游戏代码
    JS写一个JS解释器
    JS中try.. catch..的用法
    使用HTML CSS和JavaScript创建图像动画
    6个强大的CSS选择器
    TypeScript 3.9稳定版本新增功能
    10个JavaScript代码片段,使你更加容易前端开发。
    BZOJ.3811.玛里苟斯(线性基)
    Bluestein's Algorithm
    AGC 002E.Candy Piles(博弈论)
  • 原文地址:https://www.cnblogs.com/holoyong/p/7392173.html
Copyright © 2020-2023  润新知