深入剖析Tomcat-自定义Tomcat(2)

233 阅读6分钟

    在上一篇《深入剖析Tomcat-自定义Tomcat(1)》中,通过三个类自定义了一个简单的Web服务器,这一篇继续对其进行改进。之前的Web服务器仅能处理静态资源请求,这一篇我们将增加对Servlet的处理。本篇的代码是在前一篇的基础上进行改进的。

一、实现Servlet

    编写一个Servlet需要实现javax.servlet.Servlet接口。Servlet接口中声明了5个方法,分别为init()、service()、destroy()、getServletConfig()、getServletInfo()。其中init()、service()、destroy()是servlet生命周期相关的方法,init()和destory()分别是servlet创建初始化和servlet被移除前调用。当一个客户端请求到达后,Servlet容器就调用相应servlet的service()方法,并将javax.servlet.servletRequest对象和javax.servlet.servletResponse对象作为参数传入。ServletRequest包含客户端Http请求的信息,ServletResponse则封装响应信息。

    以下实现的Servlet功能非常简单,当接收到请求后,通过PrintWriter向客户端返回HTTP协议的“Hello Servlet”字符串。

public class PrimitiveServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("servlet初始化...");
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("service方法被执行...");
        PrintWriter out = response.getWriter();
        out.println("HTTP/1.1 200 OK");
        out.println("Content-Type: text/html");
        out.println("");
        out.println("<html><body><h1>Hello Servlet</h1></body></html>");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("servlet被销毁了...");
    }
}

二、Web服务器

    我们在上一篇的Web服务器的基础上,增加StaticResourceProcessor和ServletProcessor1,分别用于处理静态资源和Servlet。整个应用主要包含这几个类:HttpServer(web服务器)、Request(请求对象)、Response(响应对象)、ServletProcessor1(servlet处理)、StaticResourceProcessor(静态资源处理)。

1. HttpServer

    当HttpServer接收请求时,程序会根据请求的Url来判断是响应静态资源还是交由Servlet处理请求。

    (1)当请求的路径为:http://ip:port/staticResource时由StaticResourceProcessor响应静态资源。

    (2)当请求路径为:http://ip:port/servlet/servletName时则由ServletProcessor1处理servlet请求。

public class HttpServer {
    /**
     * 关闭命令。若要关闭服务器,可以在请求路径后面拼接SHUTDOWN命令,比如:http://localhost:8080/SHUTDOWN
     */
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    /**
     * 是否收到关闭的命令
     */
    private boolean shutdown = false;

    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        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);
        }
        while (!shutdown) {
            Socket socket;
            InputStream input;
            OutputStream output;
            try {
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();
                Request request = new Request(input);
                request.parse();

                Response response = new Response(output);
                response.setRequest(request);

                /**
                 * 改进代码:判断是否是servlet,如果请求路径以/servlet/开头表示是一个servlet,否则是请求一个静态资源
                 */
                if (request.getUri().startsWith("/servlet/")) {
                    ServletProcessor1 processor = new ServletProcessor1();
                    processor.process(request, response);
                } else {
                    StaticResourceProcessor processor = new StaticResourceProcessor();
                    processor.process(request, response);
                }

                socket.close();
                // 接收到关闭命令
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

}

    上面的代码在上一篇的基础上主要增加了这一段逻辑处理:

if (request.getUri().startsWith("/servlet/")) {
    ServletProcessor1 processor = new ServletProcessor1();
    processor.process(request, response);
} else {
    StaticResourceProcessor processor = new StaticResourceProcessor();
    processor.process(request, response);
}

2. Request

    继承javax.servlet.ServletRequest。基本功能与上一篇一致。

public class Request implements ServletRequest {

    private String uri;
    private InputStream input;

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

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

    /**
     * 用于解析http请求中的原始数据
     */
    public void parse() {
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        } catch (Exception e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j = 0; j < i; j++) {
            request.append((char) buffer[j]);
        }
        System.out.println(request);
        uri = parseUri(request.toString());
    }

    /**
     * 从http请求行中获取uri
     *
     * @param requestString
     * @return
     */
    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;
    }
 	/**省略其他实现方法*/
    ......
}

3. Response

    继承javax.servlet.ServletRequest。基本功能与上一篇一致。对getWriter()创建一个PrintWriter对象并返回。

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

    /**
     * 返回一个静态资源到浏览器
     */
    public void sendStaticResource() {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
            File file = new File(Constants.WEB_ROOT, request.getUri());
            if (file.exists()) {
                String header = "HTTP/1.1 200 OK\n" +
                        "Content-Type: text/html\n" +
                        "\r\n";
                output.write(header.getBytes());
                fis = new FileInputStream(file);
                int ch = fis.read(bytes, 0, BUFFER_SIZE);
                while (ch != -1) {
                    output.write(bytes, 0, ch);
                    ch = fis.read(bytes, 0, BUFFER_SIZE);
                }
            } else {
                String errorMessage = "HTTP/1.1 404 File Not Found\n" +
                        "Content-Type: text/html\n" +
                        "\r\n" +
                        "<html><body><h1>File Not Found</h1></body></html>";

                output.write(errorMessage.getBytes());
                output.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    @Override
    public PrintWriter getWriter() throws IOException {
        writer = new PrintWriter(output, true);
        return writer;
    }
     /**省略其他实现方法*/
    ......
}

4. Servlet处理

    ServletProcessor1这个类主要是处理Servlet的请求。

    (1)首先从请求的URL中获取servlet的类名,在前面设计的URL:http://ip:port/servlet/servletName中servletName即为servlet的类名。

    (2)然后通过URLClassLoader在Constants.WEB_ROOT设置的路径下查找servlet类。通过loader.loadClass(servletName)载入类。

    (3)最后创建servlet示例并调用其service()方法。

public class ServletProcessor1 {

    public void process(Request request, Response response) {
        String uri = request.getUri();
        // 获取一个servlet得类名
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        // 类载入器
        URLClassLoader loader = null;
        try {
            URL[] urls = new URL[1];
            File classPath = new File(Constants.WEB_ROOT);
            // 类载入器查找servlet的目录
            String repository = new URL("file", null, classPath.getCanonicalPath() + File.separator).toString();
            urls[0] = new URL(null, repository);

            loader = new URLClassLoader(urls);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Class myClass = null;
        try {
            // 这里会先从target/class下找,找不到才会在webroot下找
            myClass = loader.loadClass(servletName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Servlet servlet = null;
        try {
            servlet = (Servlet) myClass.newInstance();
            servlet.service(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

    编写好servlet后,将其编译成class,然后放入Constants.WEB_ROOT设置的路径下。

5. 静态资源处理

public class StaticResourceProcessor {
    public void process(Request request, Response response) {
        try {
            response.sendStaticResource();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6. 查看效果

    启动项目并访问:http://localhost:8080/servlet/PrimitiveServlet

三、存在的问题

1. 问题描述

    上面应用程序存在一个严重的问题。ServletProcessor1中的process()方法。会将Request对象和Response对象向上造型为javax.servlet.ServletRequest和javax.servlet.ServletResponse。

try {
    servlet = (Servlet) myClass.newInstance();
    servlet.service(request, response);
}

    这是非常不安全的做法,了解原理的程序员在自己的Servlet中可以再向下造型为Request和Response对象,就能使用其内部的parse()和sendStaticResource()方法。比如:

@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    Request res = (Request) request;
    res.parse();
}

    但我们却又不能将parse()、sendStaticResource()设置为私有方法,因为他们会被其他类调用。所以针对这个问题我们对其做出改进。

2. 改进措施

    我们增加两个包装类RequestFacade和ResponseFacade,作为对Request和Response的封装。包装类分别实现javax.servlet.ServletRequest和javax.servlet.ServletResponse。包装类所有方法都调用Request和Response的对应方法。这样,在servlet中只能调用包装类的方法了。

  • RequestFacade
public class RequestFacade implements ServletRequest {

    private ServletRequest request = null;

    public RequestFacade(ServletRequest request) {
        this.request = request;
    }

    @Override
    public Object getAttribute(String s) {
        return request.getAttribute(s);
    }
     /**省略其他实现方法*/
    ......
}
  • ResponseFacade
public class ResponseFacade implements ServletResponse {
    private ServletResponse response;

    public ResponseFacade(ServletResponse response) {
        this.response = response;
    }

    @Override
    public String getCharacterEncoding() {
        return response.getCharacterEncoding();
    }
    /**省略其他实现方法*/
    ......
}
  • ServletProcessor1

    ServletProcessor1中的process()方法修改为使用包装类。

Servlet servlet = null;
try {
    servlet = (Servlet) myClass.newInstance();
    /**
      * 这里改为使用包装类
      */
    RequestFacade requestFacade = new RequestFacade(request);
    ResponseFacade responseFacade = new ResponseFacade(response);
    servlet.service(requestFacade, responseFacade);
}