源码学习-Tomcat-02-一个简单的Servlet容器

本文会尝试介绍怎么实现一个简单的响应静态资源请求,或者servlet请求的Servlet容器。

预备知识:

  • HTTP 的请求格式:

方法—统一资源标识符(URI)—协议/版本

请求的头部

主体内容

比如:

GET /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) QQBrowser/8.2.4258.400
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive

name=shouhui

  • jvm类加载机制:

 

Web 服务器也称为超文本传输协议(HTTP)服务器,因为它使用 HTTP 来跟客户端进行通信的,这通常是个 web 浏览器。一个基于 java 的 web 服务器使用两个重要的类:java.net.Socket 和 java.net.ServerSocket,并通过 HTTP 消息进行通信。

一个全功能的 servlet 容器会为 servlet 的每个 HTTP 请求做下面一些工作:

  • 当第一次调用 servlet 的时候,加载该 servlet 类并调用 servlet 的 init 方法(仅仅一次)。
  • 对每次请求,构造一个 javax.servlet.ServletRequest 实例和一个javax.servlet.ServletResponse 实例。
  • 调用 servlet 的 service 方法,同时传递 ServletRequest 和 ServletResponse 对象。
  • 当 servlet 类被关闭的时候,调用 servlet 的 destroy 方法并卸载 servlet 类。

本文的 servlet 容器不是全功能的。因此,她不能运行什么除了非常简单的 servlet,而且也不调用 servlet 的 init 方法和 destroy 方法。相反它做了下面的事情:

  • 等待 HTTP 请求。 
  • 构造一个 ServletRequest 对象和一个 ServletResponse 对象。
  • 假如该请求需要一个静态资源的话,调用 StaticResourceProcessor 实例的 process 方法,同时传递 ServletRequest 和 ServletResponse 对象。
  • 假如该请求需要一个 servlet 的话,加载 servlet 类并调用 servlet 的 service 方法,同时传递 ServletRequest 和 ServletResponse 对象。

 

本实现的Servlet容器类图如下:

ServletTest

本实现的Servlet容器时序图如下(  按照自己的理解画的,可能会有错误的地方 ):

 

ServletTestSeq

本实现的main函数在HttpServer1类中,在创建HttpServer1类对象后,首先调用该对象的await()方法。之后创建ServerSocket对象,用于监听127.0.0.1地址的8080端口,

serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));

构造函数的第二个参数int backlog表示能够处理的最大连接请求个数,当连接请求到来,如果queue未满,则完成服务器端的协议操作,并把该连接添加至该queue。下面摘自《Fundamental Networking in Java》

TCP itself can get ahead of a server application in accepting connections. It maintains a ‘backlog queue’ of connections to a listening socket which iself has completed but which have not yet been accepted by the application.This queue exists between the underlying implementation and the server process which created the listening socket. The purpose of pre-completing connections is to speed up the connection phase, but the queue is limited in length so as not to pre-form too many connections to servers which are not accepting them at the same rate for any reason. When an incoming connection request is received and the backlog queue is not full, completes the connection protocol and adds the connection to the backlog queue. At this point, the client application is fully connected, but the server application has not yet received the connection as a result value of ServerSocket.accept. When it does so, the entry is removed from the queue.

之后进入循环等待新的连接到来,直到请求的uri为"/SHUTDOWN"。请求到来时候可以获得accept方法返回的对应socket,之后我们可以利用该socket中的InputStream,OutputStream对象与客户端浏览器进行通信。首先我们定义Request ,Response 类,分别继承自javax.servlet.ServletResquest,javax.servlet.ServletResponse。可以从InputStream取出来的HTTP请求头信息中解析出来uri,根据该uri是否以"/servlet/"开头可以选择不同的处理分支:如果是则为Servlet请求,新建ServletProcessor1对象,并调用其process(request, response)方法进行处理;否则为静态资源请求,新建StaticResourceProcessor对象,并调用process(request, response)方法进行处理。

处理Servlet请求的process方法首先定义加载指定路径,这里是webroot目录,的类加载器。之后显式的加载uri中对应的Servlet类,并利用反射新建对应的Servlet对象进行处理;处理静态资源则直接调用response对象的sendStaticResource()方法,找到uri中对应的静态资源文件,写入socket连接对应的OutputStream对象,发送到客户端浏览器。

至此,本次请求结束,进入下次循环。

操作:

访问指定静态页面:http://localhost:8080/index.html

访问指定Servlet类:http://localhost:8080/servlet/PrimitiveServlet

停止该Servlet容器的运行:http://localhost:8080/SHUTDOWN  

 

参考文章:

百度百科,servlet: http://baike.baidu.com/link?url=-WtPi8Z5ygr6g1gGDmtM3ObRwVwhoR7h-7lmBR-KX8kO5GsrTIxHtvkrMG2TW4MjtRRjUGnTfbCT9Fy2y0v4Sq

Budi Kurniawan, Paul Deck. 深入剖析Tomcat (M). 北京: 机械工业出版社华章公司, 2011.

Esmond Pitt. Fundamental Networking in Java (M). Springer, 2006.

参考代码:

public class HttpServer1 {

    /**
     * WEB_ROOT is the directory where our HTML and other files reside. For this
     * package, WEB_ROOT is the "webroot" directory under the working directory.
     * The working directory is the location in the file system from where the
     * java command was invoked.
     */
    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

    // the shutdown command received
    private boolean shutdown = false;

    public static void main(String[] args) {
        HttpServer1 server = new HttpServer1();
        server.await();
    }

    public void await() {
        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);
        }

        // Loop waiting for a request
        while (!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            try {
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();

                // create Request object and parse
                Request request = new Request(input);
                request.parse();

                // create Response object
                Response response = new Response(output);
                response.setRequest(request);

                // check if this is a request for a servlet or a static resource
                // a request for a servlet begins with "/servlet/"
                if (request.getUri().startsWith("/servlet/")) {
                    ServletProcessor1 processor = new ServletProcessor1();
                    processor.process(request, response);
                } else {
                    StaticResourceProcessor processor = new StaticResourceProcessor();
                    processor.process(request, response);
                }

                // Close the socket
                socket.close();
                // check if the previous URI is a shutdown command
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }
}
public class Request implements ServletRequest {

    private InputStream input;
    private String uri;

    public Request(InputStream input) {
        this.input = input;
    }

    public String getUri() {
        return uri;
    }

    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public void parse() {
        // Read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j = 0; j < i; j++) {
            request.append((char) buffer[j]);
        }
        System.out.print(request.toString());
        uri = parseUri(request.toString());
    }

    /* implementation of the ServletRequest */
    public Object getAttribute(String attribute) {
        return null;
    }

    public Enumeration getAttributeNames() {
        return null;
    }

    public String getRealPath(String path) {
        return null;
    }

    public RequestDispatcher getRequestDispatcher(String path) {
        return null;
    }

    public boolean isSecure() {
        return false;
    }

    public String getCharacterEncoding() {
        return null;
    }

    public int getContentLength() {
        return 0;
    }

    public String getContentType() {
        return null;
    }

    public ServletInputStream getInputStream() throws IOException {
        return null;
    }

    public Locale getLocale() {
        return null;
    }

    public Enumeration getLocales() {
        return null;
    }

    public String getParameter(String name) {
        return null;
    }

    public Map getParameterMap() {
        return null;
    }

    public Enumeration getParameterNames() {
        return null;
    }

    public String[] getParameterValues(String parameter) {
        return null;
    }

    public String getProtocol() {
        return null;
    }

    public BufferedReader getReader() throws IOException {
        return null;
    }

    public String getRemoteAddr() {
        return null;
    }

    public String getRemoteHost() {
        return null;
    }

    public String getScheme() {
        return null;
    }

    public String getServerName() {
        return null;
    }

    public int getServerPort() {
        return 0;
    }

    public void removeAttribute(String attribute) {
    }

    public void setAttribute(String key, Object value) {
    }

    public void setCharacterEncoding(String encoding)
            throws UnsupportedEncodingException {
    }

    @Override
    public int getRemotePort() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public String getLocalName() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getLocalAddr() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getLocalPort() {
        // TODO Auto-generated method stub
        return 0;
    }

}
public class Response implements ServletResponse {

    private static final int BUFFER_SIZE = 1024;
    Request request;
    OutputStream output;
    PrintWriter writer;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    /* This method is used to serve a static page */
    public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
            /* request.getUri has been replaced by request.getRequestURI */
            File file = new File(Constants.WEB_ROOT, request.getUri());
            fis = new FileInputStream(file);
            /*
             * HTTP Response = Status-Line(( general-header | response-header |
             * entity-header ) CRLF) CRLF [ message-body ] Status-Line =
             * HTTP-Version SP Status-Code SP Reason-Phrase CRLF
             */
            int ch = fis.read(bytes, 0, BUFFER_SIZE);
            while (ch != -1) {
                output.write(bytes, 0, ch);
                ch = fis.read(bytes, 0, BUFFER_SIZE);
            }
        } catch (FileNotFoundException e) {
            String errorMessage = "HTTP/1.1 404 File Not Found\r\n"
                    + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n"
                    + "\r\n" + "<h1>File Not Found</h1>";
            output.write(errorMessage.getBytes());
        } finally {
            if (fis != null)
                fis.close();
        }
    }

    /** implementation of ServletResponse */
    public void flushBuffer() throws IOException {
    }

    public int getBufferSize() {
        return 0;
    }

    public String getCharacterEncoding() {
        return null;
    }

    public Locale getLocale() {
        return null;
    }

    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    public PrintWriter getWriter() throws IOException {
        // autoflush is true, println() will flush,
        // but print() will not.
        writer = new PrintWriter(output, true);
        return writer;
    }

    public boolean isCommitted() {
        return false;
    }

    public void reset() {
    }

    public void resetBuffer() {
    }

    public void setBufferSize(int size) {
    }

    public void setContentLength(int length) {
    }

    public void setContentType(String type) {
    }

    public void setLocale(Locale locale) {
    }

    @Override
    public String getContentType() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void setCharacterEncoding(String charset) {
        // TODO Auto-generated method stub

    }
}
public class ServletProcessor1 {

    public void process(Request request, Response response) {

        String uri = request.getUri();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        URLClassLoader loader = null;

        try {
            // create a URLClassLoader
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File(Constants.WEB_ROOT);
            // the forming of repository is taken from the createClassLoader
            // method in
            // org.apache.catalina.startup.ClassLoaderFactory
            String repository = (new URL("file", null,
                    classPath.getCanonicalPath() + File.separator)).toString();
            // the code for forming the URL is taken from the addRepository
            // method in
            // org.apache.catalina.loader.StandardClassLoader class.
            urls[0] = new URL(null, repository, streamHandler);
            loader = new URLClassLoader(urls);
        } catch (IOException e) {
            System.out.println(e.toString());
        }
        Class myClass = null;
        try {
            myClass = loader.loadClass(servletName);
        } catch (ClassNotFoundException e) {
            System.out.println(e.toString());
        }

        Servlet servlet = null;

        try {
            servlet = (Servlet) myClass.newInstance();
            servlet.service((ServletRequest) request,
                    (ServletResponse) response);
        } catch (Exception e) {
            System.out.println(e.toString());
        } catch (Throwable e) {
            System.out.println(e.toString());
        }

    }
}
public class StaticResourceProcessor {

    public void process(Request request, Response response) {
        try {
            response.sendStaticResource();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
posted @ 2015-08-27 16:56  huangshouhui  阅读(362)  评论(0编辑  收藏  举报