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

2019-12-04  本文已影响0人  抬头挺胸才算活着

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

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

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;
  }
上一篇下一篇

猜你喜欢

热点阅读