• How Tomcat Works Connector


    在前两章实现的WebServer还有很多问题,比如:

    1)最后一个out.print("xxx")没有生效。

    2)没有解析请求头,请求方法,协议,uri,参数等,而这些内容在servlet里面是需要用到的。

    在这一章中,增加了一个新的servlet:ModerServlet:

    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Enumeration;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class ModernServlet extends HttpServlet {
    
      public void init(ServletConfig config) {
        System.out.println("ModernServlet -- init");
      }
    
      public void doGet(HttpServletRequest request, 
        HttpServletResponse response) 
        throws ServletException, IOException {
        
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Modern Servlet</title>");
        out.println("</head>");
        out.println("<body>");
    
        out.println("<h2>Headers</h2");
        Enumeration headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
          String header = (String) headers.nextElement();
          out.println("<br>" + header + " : " + request.getHeader(header));
        }
    
        out.println("<br><h2>Method</h2");
        out.println("<br>" + request.getMethod());
    
        out.println("<br><h2>Parameters</h2");
        Enumeration parameters = request.getParameterNames();
        while (parameters.hasMoreElements()) {
          String parameter = (String) parameters.nextElement();
          out.println("<br>" + parameter + " : " + request.getParameter(parameter));
        }
    
        out.println("<br><h2>Query String</h2");
        out.println("<br>" + request.getQueryString());
    
        out.println("<br><h2>Request URI</h2");
        out.println("<br>" + request.getRequestURI());
    
        out.println("</body>");
        out.println("</html>");
    
      }
    }
    

    这个Servlet和之前两章的PrimitiveServlet不同的是,这个Servlet是继承了HttpServlet而不是实现了Servlet接口。HttpServlet又继承了GenericServlet,实现了Servlet, ServletConfig这两个接口。所以ModerServlet也实现了Servlet接口,而且除此之外还复用了基类的功能。

    WebServer的启动和业务逻辑分成了三个类:Bootstrap,HttpProcessor和HttpConnector。HttpConnector实现了Runnable接口,这样就可以把自身的实例通过线程去去执行:

    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class HttpConnector implements Runnable {
    
        boolean stopped;
        private String scheme = "http";
    
        public String getScheme() {
            return scheme;
        }
    
        public void start() {
            Thread thread = new Thread(this);
            thread.start();
        }
    
        @Override
        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) {
                Socket socket = null;
                try {
                    socket = serverSocket.accept();
                } catch (IOException e) {
                    continue;
                }
                HttpProcessor processor = new HttpProcessor(this);
                processor.process(socket);
            }
        }
    }
    

    这部分逻辑和前两章类似,除了新增了一个HttpProcessor,这个类是处理http请求的关键部分。首先来看process函数:

        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();
            }
        }
    

    和HttpServer不一样的是:

    1)input类型又InputStream变成了SocketInputStream,这个类tomcat4里面对InputStream的封装类,在现在的tomcat源代码里面这个类已经被移除了。这里直接把这个类的源代码和它依赖的HttpRequestLine和HttpHeader拷贝进来了。同时SocketInputStream这个类还使用了org.apache.catalina.util.RequestUtil 和org.apache.catalina.util.StringManager这两个不包含在源代码里的类,可以去这里下载http://archive.apache.org/dist/tomcat/tomcat-4/v4.1.40/bin/ tomcat的zip版本,在server/lib下找到catalina.jar并加到classpath里面。

    2)增加了解析request和header的逻辑:

          response.setHeader("Server", "Pyrmont Servlet Container");
    
          parseRequest(input, output);
          parseHeaders(input)

    parseRequest就是解析Http请求的第一部分,比如:

    GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
    

    然后把解析到的结果保存到HttpRequest对象中:

        private void parseRequest(SocketInputStream input, OutputStream output)
                throws IOException, ServletException {
    
            // Parse the incoming request line
            input.readRequestLine(requestLine);
    // http请求方法,这里是GET String method = new String(requestLine.method, 0, requestLine.methodEnd); String uri = null;
    // http请求协议,这里是http/1.1 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"); }
    // 解析问号后面的部分,比如/myApp/ModernServlet?userName=tarzan&password=pwd 解析之后uri成了/myApp/ModernServlet
    // ,userName=tarzan&password=pwd保存在HttpRequest的queryString中 // 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); } // 如果uri是一个绝对路径,比如http://baidu.com/index.html?abc=xyz则把http://baidu.com这一部分去掉 // 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); } } } // 从uri里解析jsessionid,如果有的话 // 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); } // 规范化uri的内容 // 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 + "'"); } }

     parseHeaders通过一个while循环,每次通过SocketInputStream读入一个header项:

        private void parseHeaders(SocketInputStream input)
                throws IOException, ServletException {
            while (true) {
                HttpHeader header = new HttpHeader();
                ;
    
                // Read the next header
                input.readHeader(header);
                if (header.nameEnd == 0) {
                    if (header.valueEnd == 0) {
                        return;
                    } else {
                        throw new ServletException
                                (sm.getString("httpProcessor.parseHeaders.colon"));
                    }
                }
    
                String name = new String(header.name, 0, header.nameEnd);
                String value = new String(header.value, 0, header.valueEnd);
                request.addHeader(name, value);
                // do something for some headers, ignore others.
                if (name.equals("cookie")) {
                    Cookie cookies[] = RequestUtil.parseCookieHeader(value);
                    for (int i = 0; i < cookies.length; i++) {
                        if (cookies[i].getName().equals("jsessionid")) {
                            // Override anything requested in the URL
                            if (!request.isRequestedSessionIdFromCookie()) {
                                // Accept only the first session id cookie
                                request.setRequestedSessionId(cookies[i].getValue());
                                request.setRequestedSessionCookie(true);
                                request.setRequestedSessionURL(false);
                            }
                        }
                        request.addCookie(cookies[i]);
                    }
                } else if (name.equals("content-length")) {
                    int n = -1;
                    try {
                        n = Integer.parseInt(value);
                    } catch (Exception e) {
                        throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
                    }
                    request.setContentLength(n);
                } else if (name.equals("content-type")) {
                    request.setContentType(value);
                }
            } //end while
        }
    

     如果header项的是cookie,使用RequestUtil.parseCookieHeader去解析cookie的值,如果cookie的值中包含jsessionid,就执行:

    // Override anything requested in the URL
                            if (!request.isRequestedSessionIdFromCookie()) {
                                // Accept only the first session id cookie
                                request.setRequestedSessionId(cookies[i].getValue());
                                request.setRequestedSessionCookie(true);
                                request.setRequestedSessionURL(false);
                            }
    

     只有request.isRequestedSessionIdFromCookie()返回false的时候才保存jsessionid的值到sessionid,但是什么时候会返回false,我们可以看下这个接口的声明:

        /**
         *
         * Checks whether the requested session ID came in as a cookie.
         *
         * @return			<code>true</code> if the session ID
         *				came in as a
         *				cookie; otherwise, <code>false</code>
         *
         *
         * @see			#getSession
         *
         */ 
    
        public boolean isRequestedSessionIdFromCookie();
    

     看起来是说sessionid是否已经从cookie中拿到了,再配合评论// Accept only the first session id cookie 所以我们知道就是一个标记位,在request.setRequestedSessionCookie(true);就是设成了true就是说只接受第一个sessionid。我们也可以去看下tommcat的源码:

        /**
         * Return <code>true</code> if the session identifier included in this
         * request came from a cookie.
         */
        @Override
        public boolean isRequestedSessionIdFromCookie() {
    
            if (requestedSessionId == null) {
                return false;
            }
    
            return requestedSessionCookie;
        }
    

    然后我们可以看到请求参数的解析是在HttpRequest的parseParameters里面做的:

        protected void parseParameters() {
            if (parsed)
                return;
            ParameterMap results = parameters;
            if (results == null)
                results = new ParameterMap();
            results.setLocked(false);
            String encoding = getCharacterEncoding();
            if (encoding == null)
                encoding = "ISO-8859-1";
    
            // Parse any parameters specified in the query string
            String queryString = getQueryString();
            try {
                RequestUtil.parseParameters(results, queryString, encoding);
            } catch (UnsupportedEncodingException e) {
                ;
            }
    
            // Parse any parameters specified in the input stream
            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 ("POST".equals(getMethod()) && (getContentLength() > 0)
                    && "application/x-www-form-urlencoded".equals(contentType)) {
                try {
                    int max = getContentLength();
                    int len = 0;
                    byte buf[] = new byte[getContentLength()];
                    ServletInputStream is = getInputStream();
                    while (len < max) {
                        int next = is.read(buf, len, max - len);
                        if (next < 0) {
                            break;
                        }
                        len += next;
                    }
                    is.close();
                    if (len < max) {
                        throw new RuntimeException("Content length mismatch");
                    }
                    RequestUtil.parseParameters(results, buf, encoding);
                } catch (UnsupportedEncodingException ue) {
                    ;
                } catch (IOException e) {
                    throw new RuntimeException("Content read fail");
                }
            }
    
            // Store the final results
            results.setLocked(true);
            parsed = true;
            parameters = results;
        }
    

    最关键的部分就是通过RequestUtil.parseParameters 解析queryString并把结果保存到results。而且这个函数通过parsed这个变量来保证只会被执行一次。

    最后我们访问http://localhost:8080/servlet/PrimitiveServlet,发现浏览器打印了:

    Hello, Roses are red
    Violets are blue
    

    说明上一章的那个最后的out.print("xxx")没有生效的问题解决了。但是访问http://localhost:8080/servlet/ModernServlet?abc=xyz,因为第一章说的问题,火狐浏览器打印了:

    <html>
    <head>
    <title>Modern Servlet</title>
    </head>
    <body>
    <h2>Headers</h2
    <br>accept-language : en-US,en;q=0.5
    <br>host : localhost:8080
    <br>upgrade-insecure-requests : 1
    <br>connection : keep-alive
    <br>accept-encoding : gzip, deflate
    <br>user-agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Firefox/52.0
    <br>accept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    <br><h2>Method</h2
    <br>GET
    <br><h2>Parameters</h2
    <br>abc : xyz
    <br><h2>Query String</h2
    <br>abc=xyz
    <br><h2>Request URI</h2
    <br>/servlet/ModernServlet
    </body>
    </html>
    

    如果需要达到想要的效果,可以通过IE浏览器访问。

  • 相关阅读:
    java web 资源文件读取
    页面跳转
    验证码的随机图片
    spring 注解
    回文字符串系列问题
    【leetcode】Find All Anagrams in a String
    斐波那契数列
    【leetcode】 First Missing Positive
    Trapping Rain Water
    区间合并问题
  • 原文地址:https://www.cnblogs.com/likely/p/7294199.html
Copyright © 2020-2023  润新知