web

Tomcat源码分析 -- 1

2019-03-07  本文已影响12人  sschrodinger

Tomcat源码分析 -- 1

sschrodinger

2018/12/10


资源



HTTP 协议


HTTP 协议建立在 TCP 连接之上,提供安全可靠的数据传输方案。

HTTP 请求

HTTP 请求由三部分组成,分别是请求行,请求头和实体,具体格式如下:

method URI protocol/version
Accept:text/plain;text/html
Accept-language:en-gb
Conection:Keep-Alive
Host:localHost
Content-length:33

lastName=Franks&firstName=Michael

note

  • HTTP 协议的所有换行符都为/r/n,即标准的 linux 换行符

HTTP 请求的第一行规定了传输方法,URI和协议/版本,称为请求行,一般有如下的形式:

POST /example/default.jsp HTTP/1.1

接下来到实体之前规定了请求头,表明了这个 HTTP 请求的属性,最后是实体,表明了传送的内容。

note

  • 在实体和请求头之间一定有一个换行符,以表明请求头的结束。
HTTP 响应

HTTP 响应也同样包括了三个部分,分别是响应行,响应头和响应实体。具体格式如下:

protocol/version statusCode description
Date:Mon, 5 Jan 2004 13:13:33 GMT
content-length:112

<html>
<head>
<title>HTTP response example</title>
</head>
<body>
Welcome to my world
</body>
</html>

HTTP 响应的第一行规定了协议/版本和状态,称为响应行,一般有如下的形式:

HTTP/1.1 200 OK

note

  • 类似的,在响应实体和响应头之间一定有一个换行符,以表明响应头的结束。

servlet 编程用户接口(servlet 框架)


在 servlet 编程中,用户主要会使用三个接口来编写 web 应用程序。回想一下,我们在 web 编程时,主要是实现一个 Servlet 的接口,接口定义如下:

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public interface Servlet {

    @Override
    public void destroy();

    @Override
    public ServletConfig getServletConfig();

    @Override
    public String getServletInfo();

    @Override
    public void init(ServletConfig arg0) throws ServletException;

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;

}

此接口一共提供了五个函数需要我们实现。

note

  • init 函数只会调用一次,这个函数可以作为数据库等的初始化函数。
  • service 函数必须在init 函数调用之后调用,这个是我们改写的主要函数,实现了具体的功能,包括来了一个请求之后如何返回一个响应。
  • destroy 函数调用之后,就不能调用 service 函数了,规定如何销毁一个 servlet。

在实现中,有辅助类 ServletRequest 和 ServletResponse,ServletRequest 提供了大量的 get 方法,以此获得 request 的属性,ServletResponse 提供了大量的 set 方法,用来设置 response 的属性。

可以直接使用实现了 Servlet 接口的类 (HttpServlet) 来完成 web 服务器的编写。定义如下:

package javax.servlet.http;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;

import javax.servlet.*;

public abstract class HttpServlet extends GenericServlet
{
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    private static final String HEADER_LASTMOD = "Last-Modified";
    
    private static final String LSTRING_FILE =
        "javax.servlet.http.LocalStrings";
    //ResourceBundle处理国际化语言
    private static ResourceBundle lStrings =
        ResourceBundle.getBundle(LSTRING_FILE);
   
    
    public HttpServlet() { }
    

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

    protected long getLastModified(HttpServletRequest req) {
        return -1;
    }
    
    protected void doHead(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        //NoBodyResponse 专门为 doHead 服务,可以不管
        NoBodyResponse response = new NoBodyResponse(resp);
        
        doGet(req, response);
        response.setContentLength();
    }


    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
       // similar with doGet
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // similar with doGet
    }

    protected void doDelete(HttpServletRequest req,
                            HttpServletResponse resp)
        throws ServletException, IOException
    {
        // similar with doGet
    }

    private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {

        Class<?> clazz = c;
        Method[] allMethods = null;

        while (!clazz.equals(HttpServlet.class)) {
            Method[] thisMethods = clazz.getDeclaredMethods();
            if (allMethods != null && allMethods.length > 0) {
                Method[] subClassMethods = allMethods;
                allMethods =
                    new Method[thisMethods.length + subClassMethods.length];
                System.arraycopy(thisMethods, 0, allMethods, 0,
                                 thisMethods.length);
                System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length,
                                 subClassMethods.length);
            } else {
                allMethods = thisMethods;
            }

            clazz = clazz.getSuperclass();
        }

        return ((allMethods != null) ? allMethods : new Method[0]);
    }

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        Method[] methods = getAllDeclaredMethods(this.getClass());
        
        boolean ALLOW_GET = false;
        boolean ALLOW_HEAD = false;
        boolean ALLOW_POST = false;
        boolean ALLOW_PUT = false;
        boolean ALLOW_DELETE = false;
        boolean ALLOW_TRACE = true;
        boolean ALLOW_OPTIONS = true;
        
        for (int i=0; i<methods.length; i++) {
            String methodName = methods[i].getName();
            
            if (methodName.equals("doGet")) {
                ALLOW_GET = true;
                ALLOW_HEAD = true;
            } else if (methodName.equals("doPost")) {
                ALLOW_POST = true;
            } else if (methodName.equals("doPut")) {
                ALLOW_PUT = true;
            } else if (methodName.equals("doDelete")) {
                ALLOW_DELETE = true;
            }
            
        }
        
        // we know "allow" is not null as ALLOW_OPTIONS = true
        // when this method is invoked
        StringBuilder allow = new StringBuilder();
        if (ALLOW_GET) {
            allow.append(METHOD_GET);
        }
        if (ALLOW_HEAD) {
            if (allow.length() > 0) {
                allow.append(", ");
            }
            allow.append(METHOD_HEAD);
        }
        if (ALLOW_POST) {
            if (allow.length() > 0) {
                allow.append(", ");
            }
            allow.append(METHOD_POST);
        }
        if (ALLOW_PUT) {
            if (allow.length() > 0) {
                allow.append(", ");
            }
            allow.append(METHOD_PUT);
        }
        if (ALLOW_DELETE) {
            if (allow.length() > 0) {
                allow.append(", ");
            }
            allow.append(METHOD_DELETE);
        }
        if (ALLOW_TRACE) {
            if (allow.length() > 0) {
                allow.append(", ");
            }
            allow.append(METHOD_TRACE);
        }
        if (ALLOW_OPTIONS) {
            if (allow.length() > 0) {
                allow.append(", ");
            }
            allow.append(METHOD_OPTIONS);
        }
        
        resp.setHeader("Allow", allow.toString());
    }

    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException
    {
        
        int responseLength;

        String CRLF = "\r\n";
        StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
            .append(" ").append(req.getProtocol());

        Enumeration<String> reqHeaderEnum = req.getHeaderNames();

        while( reqHeaderEnum.hasMoreElements() ) {
            String headerName = reqHeaderEnum.nextElement();
            buffer.append(CRLF).append(headerName).append(": ")
                .append(req.getHeader(headerName));
        }

        buffer.append(CRLF);

        responseLength = buffer.length();

        resp.setContentType("message/http");
        resp.setContentLength(responseLength);
        ServletOutputStream out = resp.getOutputStream();
        out.print(buffer.toString());
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

    private void maybeSetLastModified(HttpServletResponse resp,
                                      long lastModified) {
        if (resp.containsHeader(HEADER_LASTMOD))
            return;
        if (lastModified >= 0)
            resp.setDateHeader(HEADER_LASTMOD, lastModified);
    }
    
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;
        
        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }

}

在 HTTP/1.1 协议中,HttpServlet 实现了 新版的全部七个 Http 请求方法,包括 get、post、head、put、delete、options、trace

note

  • 在 HttpServlet 的实现中,使用了继承自 ServletResponse/ServletRequest 的 HttpServletResponse/HttpServletRequest,增加了 getMethod 等获得 Http 请求方法等属性的方法。
  • 实现了 Servlet 接口的函数为 public void service(ServletRequest req, ServletResponse res),这个函数只是检查 request 和 response 参数是不是 Http* 类型,最终的操作是交给另一个同名函数protected void service(HttpServletRequest req, HttpServletResponse resp)来做。
  • protected void service(HttpServletRequest req, HttpServletResponse resp)根据 http 请求的方法不同,将不同的 http 请求分发给不同的 doFunction 来做。所以在实际中是需要重写需要的 doFunction 方法,比如说重写 doGet 以接收并处理使用 get 方式传来的数据。

一个最简单的 hello world 服务器端如下所示:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {
 
  private String message;

  public void init() throws ServletException
  {
      message = "Hello World";
  }

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
            throws ServletException, IOException
  {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      out.println("<h1>" + message + "</h1>");
  }
  
  public void destroy(){}
}

服务器端处理流程


如下是一个极简的服务器处理框架:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer {
    
    public static void main(String[] args) {
        HttpServer httpServer = new HttpServer();
        httpServer.await();
    }
    
    public void await() {
        ServerSocket serverSocket = null;
        
        try {
            serverSocket = new ServerSocket(Constants.PORT, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.exit(1);
        }
        
        while (true) {
            try (Socket socket = serverSocket.accept();
                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream()){
                Request request = new Request(inputStream);
                Response response = new Response(outputStream);
                
                if (request.getUri().startsWith("/servlet/")) {
                    //如果请求的url为servlet程序
                    //动态加载 servlet
                    Class clazz = Class.forName("xxx");
                    Servlet servlet = (HttpServlet)clazz.newInstance();
                    //处理 clazz 的service 方法
                    servlet.service(request, response);
                } else {
                    //静态页面,直接发送
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }

}

Java 的反射机制很好的将用户接口和服务器连接起来。
总结服务器的工作流程如下:

graph LR
创建ServerSocket-->监听ServerSocket
监听ServerSocket-->当有新连接时创建Socket
当有新连接时创建Socket-->填充Request和Response
填充Request和Response-->如果是servlet则动态加载servlet处理
上一篇下一篇

猜你喜欢

热点阅读