How Tomcat Works - Connector

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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接口,这样就可以把自身的实例通过线程去去执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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的逻辑:

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
private void parseRequest(SocketInputStream input, OutputStream output)
        throws IOException, ServletException {
 
    // Parse the incoming request line
    input.readRequestLine(requestLine);<br>        // http请求方法,这里是GET
    String method =
            new String(requestLine.method, 0, requestLine.methodEnd);
    String uri = null;<br>        // 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");
    }<br>        // 解析问号后面的部分,比如/myApp/ModernServlet?userName=tarzan&password=pwd 解析之后uri成了/myApp/ModernServlet<br>        // ,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项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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,就执行:

1
2
3
4
5
6
7
// 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,我们可以看下这个接口的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 *
 * 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的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 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里面做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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,发现浏览器打印了:

1
2
Hello, Roses are red
Violets are blue

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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浏览器访问。

posted @   liketic  阅读(214)  评论(0编辑  收藏  举报
编辑推荐:
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Linux系统下SQL Server数据库镜像配置全流程详解
· 现代计算机视觉入门之:什么是视频
· 你所不知道的 C/C++ 宏知识
阅读排行:
· 不到万不得已,千万不要去外包
· C# WebAPI 插件热插拔(持续更新中)
· 会议真的有必要吗?我们产品开发9年了,但从来没开过会
· 【译】我们最喜欢的2024年的 Visual Studio 新功能
· 如何打造一个高并发系统?
点击右上角即可分享
微信分享提示