美文网首页
深入剖析Tomcat(How Tomcat works)读书笔记

深入剖析Tomcat(How Tomcat works)读书笔记

作者: 抬头挺胸才算活着 | 来源:发表于2019-12-04 21:40 被阅读0次

    参考资料:
    [1]. 深入剖析Tomcat(How Tomcat works)书籍代码下载地址

    注意:这一章的内容是基于前一章的内容。

    • 第三章:连接器
      StringManager类将错误消息存储在LocalStrings_xxx.properties文件中,xxx是各个国家的简写,每个包下面每个国家各有一个,随便找一个中文的来看下,如下面所示。
    jasper.error.emptybodycontent.nonempty=根据 TLD,[{0}] 标签必须为空,但不是
    
    jsp.error.action.isnottagfile=[{0}]行为只能用于标签文件
    jsp.error.attempt_to_clear_flushed_buffer=错误:尝试清空已刷新的缓冲区
    jsp.error.attribute.deferredmix=不能在同一属性值中同时使用 ${} 和 #{} EL 表达式
    jsp.error.attribute.noequal=期望的符号是等号
    jsp.error.attribute.noescape=属性值[{0}]引用[{1}],在值内使用时必须被转义。
    jsp.error.attribute.nowhitespace=JSP 规范要求一个属性名字前有空格
    jsp.error.bad.scratch.dir=你指定的 scratchDir:[{0}] 不可用。
    jsp.error.bad_attribute=属性[{0}]无效为tag[{1}] 通过TLD
    ...
    

    StringManager的用法

    public class UpgradeServletOutputStream extends ServletOutputStream {
    
        private static final StringManager sm =
                StringManager.getManager(UpgradeServletOutputStream.class);
    
        @Override
        public final boolean isReady() {
            if (listener == null) {
                throw new IllegalStateException(
                        sm.getString("upgrade.sos.canWrite.ise"));
            }
            ...
        }
    }
    

    我们就来看看StringManager内部的实现,上面讲了StringManager每个包一个,上面虽然输入的参数是类,但是最后还是会得到它的包名再去获取对应的StringManager,接下来我们来看看getManager的真正实现。
    输入的是包名和地区的名字,StringManger实际上放在Map中Map中,第一层Map是包名->Map,第二层Map是地区名->StringManager。removeEldestEntry那段读下上面的英文即可知,是为了保证第二层Map的容量固定。最后如果找不到,那么根据包名和地区新创建一个StringManager。

        /**
         * Get the StringManager for a particular package and Locale. If a manager
         * for a package/Locale combination already exists, it will be reused, else
         * a new StringManager will be created and returned.
         *
         * @param packageName The package name
         * @param locale      The Locale
         *
         * @return The instance associated with the given package and Locale
         */
        public static final synchronized StringManager getManager(
                String packageName, Locale locale) {
    
            Map<Locale,StringManager> map = managers.get(packageName);
            if (map == null) {
                /*
                 * Don't want the HashMap to be expanded beyond LOCALE_CACHE_SIZE.
                 * Expansion occurs when size() exceeds capacity. Therefore keep
                 * size at or below capacity.
                 * removeEldestEntry() executes after insertion therefore the test
                 * for removal needs to use one less than the maximum desired size
                 *
                 */
                map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    protected boolean removeEldestEntry(
                            Map.Entry<Locale,StringManager> eldest) {
                        if (size() > (LOCALE_CACHE_SIZE - 1)) {
                            return true;
                        }
                        return false;
                    }
                };
                managers.put(packageName, map);
            }
    
            StringManager mgr = map.get(locale);
            if (mgr == null) {
                mgr = new StringManager(packageName, locale);
                map.put(locale, mgr);
            }
            return mgr;
        }
    

    接下来我们来看看StringManager的创建,

    /**
     * Creates a new StringManager for a given package. This is a
     * private method and all access to it is arbitrated by the
     * static getManager method call so that only one StringManager
     * per package will be created.
     *
     * @param packageName Name of package to create StringManager for.
     */
    private StringManager(String packageName, Locale locale) {
        String bundleName = packageName + ".LocalStrings";
        ResourceBundle bnd = null;
        try {
            // The ROOT Locale uses English. If English is requested, force the
            // use of the ROOT Locale else incorrect results may be obtained if
            // the system default locale is not English and translations are
            // available for the system default locale.
            if (locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
                locale = Locale.ROOT;
            }
            bnd = ResourceBundle.getBundle(bundleName, locale);
        } catch (MissingResourceException ex) {
            // Try from the current loader (that's the case for trusted apps)
            // Should only be required if using a TC5 style classloader structure
            // where common != shared != server
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            if (cl != null) {
                try {
                    bnd = ResourceBundle.getBundle(bundleName, locale, cl);
                } catch (MissingResourceException ex2) {
                    // Ignore
                }
            }
        }
        bundle = bnd;
        // Get the actual locale, which may be different from the requested one
        if (bundle != null) {
            Locale bundleLocale = bundle.getLocale();
            if (bundleLocale.equals(Locale.ROOT)) {
                this.locale = Locale.ENGLISH;
            } else {
                this.locale = bundleLocale;
            }
        } else {
            this.locale = null;
        }
    }
    

    上一节中自己创建了Request,继承自RequestServlet,这节创建了HttpRequest,继承自HttpRequestServlet。上一节HttpServer负责等待http 请求,并创建 request 和 response 对象。本章中,等待 http 请求的工作由 HttpConnector 完成,创建 request 和 response 对象的工作由 HttpProcessor 完成,HttpProcessor解析请求行和请求头的信息,HttpConnector在用户需要的时候解析请求体或者查询字符串中的参数。
    HttpRequest包含很多请求头的很多信息,但是他们不一定会被用到,所以用到的时候才会进行加载。
    函数从HttpConnector开始,HttpConnector创建一个新的线程,然后建立ServerSocket等待连接,之后交给HttpProcessor处理。
    Bootstrap.java

    public final class Bootstrap {
      public static void main(String[] args) {
        HttpConnector connector = new HttpConnector();
        connector.start();
      }
    }
    

    HttpConnector.java

    public class HttpConnector implements Runnable {
    
      boolean stopped;
      private String scheme = "http";
    
      public String getScheme() {
        return scheme;
      }
    
      public void run() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
          serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        }
        catch (IOException e) {
          e.printStackTrace();
          System.exit(1);
        }
        while (!stopped) {
          // Accept the next incoming connection from the server socket
          Socket socket = null;
          try {
            socket = serverSocket.accept();
          }
          catch (Exception e) {
            continue;
          }
          // Hand this socket off to an HttpProcessor
          HttpProcessor processor = new HttpProcessor(this);
          processor.process(socket);
        }
      }
    
      public void start() {
        Thread thread = new Thread(this);
        thread.start();
      }
    }
    

    HttpProcessor.proccess创建HttpRequest,HttpResponse,然后解析请求头信息,分派到Servlet。

      public void process(Socket socket) {
        SocketInputStream input = null;
        OutputStream output = null;
        try {
          input = new SocketInputStream(socket.getInputStream(), 2048);
          output = socket.getOutputStream();
    
          // create HttpRequest object and parse
          request = new HttpRequest(input);
    
          // create HttpResponse object
          response = new HttpResponse(output);
          response.setRequest(request);
    
          response.setHeader("Server", "Pyrmont Servlet Container");
    
          parseRequest(input, output);
          parseHeaders(input);
    
          //check if this is a request for a servlet or a static resource
          //a request for a servlet begins with "/servlet/"
          if (request.getRequestURI().startsWith("/servlet/")) {
            ServletProcessor processor = new ServletProcessor();
            processor.process(request, response);
          }
          else {
            StaticResourceProcessor processor = new StaticResourceProcessor();
            processor.process(request, response);
          }
    
          // Close the socket
          socket.close();
          // no shutdown for this application
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    

    parseRequest(input, output);的代码如下所示。input.readRequestLine(requestLine);是将请求行的请求方法,URI,协议都给提取出来,放在HttpRequestLine类中。input的类型是SocketInputStream,它代理了InputStream类型,增加了简单解析请求行readRequestLine和简单解析请求头readHeader两个方法,然后HttpProcessor再此基础上分别在parseRequest和parseHeaders方法进一步进行信息的提取和规整化等等。下面parseRequest再得到请求行的各个信息后,再分别进行检验是否为空,对URI的处理比较浓墨重彩,因为URI可以携带很多信息,包括请求参数,jsessionid,绝对路径相对路径的处理,错误路径的矫正等等。

        private void parseRequest(SocketInputStream input, OutputStream output)
                throws IOException, ServletException {
    
            // Parse the incoming request line
            input.readRequestLine(requestLine);
            String method = new String(requestLine.method, 0, requestLine.methodEnd);
            String uri = null;
            String protocol = new String(requestLine.protocol, 0,
                    requestLine.protocolEnd);
    
            // Validate the incoming request line
            if (method.length() < 1) {
                throw new ServletException("Missing HTTP request method");
            } else if (requestLine.uriEnd < 1) {
                throw new ServletException("Missing HTTP request URI");
            }
            // Parse any query parameters out of the request URI
            int question = requestLine.indexOf("?");
            if (question >= 0) {
                request.setQueryString(new String(requestLine.uri, question + 1,
                        requestLine.uriEnd - question - 1));
                uri = new String(requestLine.uri, 0, question);
            } else {
                request.setQueryString(null);
                uri = new String(requestLine.uri, 0, requestLine.uriEnd);
            }
    
            // Checking for an absolute URI (with the HTTP protocol)
            if (!uri.startsWith("/")) {
                int pos = uri.indexOf("://");
                // Parsing out protocol and host name
                if (pos != -1) {
                    pos = uri.indexOf('/', pos + 3);
                    if (pos == -1) {
                        uri = "";
                    } else {
                        uri = uri.substring(pos);
                    }
                }
            }
    
            // Parse any requested session ID out of the request URI
            String match = ";jsessionid=";
            int semicolon = uri.indexOf(match);
            if (semicolon >= 0) {
                String rest = uri.substring(semicolon + match.length());
                int semicolon2 = rest.indexOf(';');
                if (semicolon2 >= 0) {
                    request.setRequestedSessionId(rest.substring(0, semicolon2));
                    rest = rest.substring(semicolon2);
                } else {
                    request.setRequestedSessionId(rest);
                    rest = "";
                }
                request.setRequestedSessionURL(true);
                uri = uri.substring(0, semicolon) + rest;
            } else {
                request.setRequestedSessionId(null);
                request.setRequestedSessionURL(false);
            }
    
            // Normalize URI (using String operations at the moment)
            String normalizedUri = normalize(uri);
    
            // Set the corresponding request properties
            ((HttpRequest) request).setMethod(method);
            request.setProtocol(protocol);
            if (normalizedUri != null) {
                ((HttpRequest) request).setRequestURI(normalizedUri);
            } else {
                ((HttpRequest) request).setRequestURI(uri);
            }
    
            if (normalizedUri == null) {
                throw new ServletException("Invalid URI: " + uri + "'");
            }
        }
    

    下面我们仔细看下SocketInputStream.readRequestLine,它将请求行的信息放到了它的参数HttpRequestLine类型变量中,下面截取它读取请求行的请求方法,在这之前已经过滤掉一些特殊的字符的干扰,接下来不断地读取输入直到遇到空格,HttpRequestLine给请求方法字段预留了空间,如果不够,需要进行翻倍拓展。请求行其他的信息提取也是类似的。

            while (!space) {
                // if the buffer is full, extend it
                if (readCount >= maxRead) {
                    if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
                        char[] newBuffer = new char[2 * maxRead];
                        System.arraycopy(requestLine.method, 0, newBuffer, 0,
                                         maxRead);
                        requestLine.method = newBuffer;
                        maxRead = requestLine.method.length;
                    } else {
                        throw new IOException
                            (sm.getString("requestStream.readline.toolong"));
                    }
                }
                // We're at the end of the internal buffer
                if (pos >= count) {
                    int val = read();
                    if (val == -1) {
                        throw new IOException
                            (sm.getString("requestStream.readline.error"));
                    }
                    pos = 0;
                    readStart = 0;
                }
                if (buf[pos] == SP) {
                    space = true;
                }
                requestLine.method[readCount] = (char) buf[pos];
                readCount++;
                pos++;
            }
    
            requestLine.methodEnd = readCount - 1;
    

    在自定义的HttpRequest中会用ParameterMap类型变量存放着请求的参数,请求的参数可能存在URI中,也可能存在请求体中,这里只有用到的时候才会用parseParameters进行加载,且只加载一次。ParameterMap继承自HashMap,增加了一个lock变量,但是貌似起不到同步的作用??

      public String getParameter(String name) {
        parseParameters();
        String values[] = (String[]) parameters.get(name);
        if (values != null)
          return (values[0]);
        else
          return (null);
      }
    
      public Map getParameterMap() {
        parseParameters();
        return (this.parameters);
      }
    
      public Enumeration getParameterNames() {
        parseParameters();
        return (new Enumerator(parameters.keySet()));
      }
    
      public String[] getParameterValues(String name) {
        parseParameters();
        String values[] = (String[]) parameters.get(name);
        if (values != null)
          return (values);
        else
          return null;
      }
    

    HttpResponse.getWriter反映了写出时的流程,ResponseWriter(实际上是PrintWriter,将字符串转化为字节流)->OutputStreamWriter(编码)->ResponseStream...写出到socket

      public PrintWriter getWriter() throws IOException {
        ResponseStream newStream = new ResponseStream(this);
        newStream.setCommit(false);
        OutputStreamWriter osr =
          new OutputStreamWriter(newStream, getCharacterEncoding());
        writer = new ResponseWriter(osr);
        return writer;
      }
    

    相关文章

      网友评论

          本文标题:深入剖析Tomcat(How Tomcat works)读书笔记

          本文链接:https://www.haomeiwen.com/subject/odehgctx.html