How Tomcat Works - A Simple Servlet Container

这一章和第一章的区别就是对servlet的支持。我们看下是怎么做的。

1)首先Response和Request这两个类分别实现了ServletResponse和ServletRequest接口,这两个接口和后面用到的Servlet接口都在javax.servlet这个package下面,需要添加servlet-api这个第三方依赖到classpath才能访问,这里用的是servlet-api-2.5。所有新加的方法除了Response.getWriter() 都没有任何处理,因为暂时没有用到。

Response.getWriter():

@Override
public PrintWriter getWriter() throws IOException {
     writer = new PrintWriter(output, true);
     return writer;
}

实例化了一个PrintWriter对象,第一个参数就是request的OutputStream,第二个参数表示是否自动flush。

2)对于静态资源请求和servlet请求分别创建了StaticResourceProcessor和ServletProcessor1。

StaticResourceProcessor:

import java.io.IOException;

public class StaticResourceProcessor {

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

这里比较简单,就是条用response的sendStaticResource这个方法。

ServletProcessor1:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;

import javax.servlet.Servlet;

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 {
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;

            File classPath = new File(Constants.WEB_ROOT);
            String repository = (new URL("file", null,
                    classPath.getCanonicalPath() + File.separator)).toString();
            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(request, response);
        } catch (Throwable e) {
            System.out.println(e.toString());
        }
    }
}

这段代码的主要目的就是通过Request的uri找到对应的Servlet类,通过URLClassLoader从文件加载这个类并通过反射实例化一个Servlet对象,然后通过Servlet对象的service方法处理请求。servlet文件暂时都是放在webroot这个文件夹。所以很明显,目前只能根据uri是否是以/servlet/开头来判断一个请求是否是servlet请求。而且对于每个请求,都必须创建一个Servlet实例。

3)然后修改HttpServer这个类,对于Servlet和静态资源请求使用不同的processor:

    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 chapter1.Request object and parse
                Request request = new Request(input);
                request.parse();

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

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

4)在webroot下创建测试用的PrimitiveServlet:

import java.io.IOException;
import java.io.PrintWriter;

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

public class PrimitiveServlet implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = servletResponse.getWriter();
        out.println("Hello, Roses are red");
        out.print("Violets are blue");
    }

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

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

启动HttpServelt访问http://localhost:8080/servlet/PrimitiveServlet 会发现(火狐)浏览器打印出了:

Hello, Roses are red

我们会发现下面这一行:

out.print("Violets are blue");

并没有打印出来,这个问题会在后面解决。

5)使用门面模式隐藏request和response的方法

这部分是讲,我们在调用servelt的时候:

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

直接传入了request和response,那么request和response里面一些public但是又不应该被servlet访问的方法可以被访问到。虽然Servlet.service的签名是:

    public void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;

但是在PrimitiveServlet里面还是可以通过向下转型得到一个Request类型的对象:

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = servletResponse.getWriter();
        out.println("Hello, Roses are red");
        out.print("Violets are blue");

        Request request = (Request) servletRequest;
        request.xxx();
        // others
    }

这样Request内部不希望被Servlet访问但是又必须是public的方法就暴露了。所以作者在这里使用门面模式把request和response做了封装,以request为例,新增了RequestFacade:

public class RequestFacade implements ServletRequest {

    private ServletRequest request;

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

    @Override
    public Object getAttribute(String s) {
        return request.getAttribute(s);
    }

    @Override
    public Enumeration getAttributeNames() {
        return request.getAttributeNames();
    }

   //.....
}

同样实现了ServletRequest接口,同时内部保留一个Request类型的引用,所有的方法都转发给真正的request。Response也是类似的处理。重构之后的ServletProcessor1:

        Servlet servlet = null;
        RequestFacade requestFacade = new RequestFacade(request);
        ResponseFacade responseFacade = new ResponseFacade(response);

        try {
            servlet = (Servlet) myClass.newInstance();
            servlet.service(requestFacade, responseFacade);
        } catch (Throwable e) {
            System.out.println(e.toString());
        }

这里传给Servlet.service()的就是Facade了,不必再担心Servlet里面会对request和respone向下转型。

时间: 2024-11-02 23:35:23

How Tomcat Works - A Simple Servlet Container的相关文章

《how tomcat work》 搬运工 charpter2: A Simple Servlet Container

这一章介绍了servlet container,application中对应的类是主要是httpServer1,这个类是创建socket绑定端口,接受请求,然后创建request实例和response实例,根据request的uri来处理请求,如果是静态资源就创建StaticResourceProcessor实例来处理,如果是servlet请求则创建对应servlet来处理请求 这章节的request和response类继承了servletRequest接口,所以对应的pom要添加依赖项 <de

how tomcat works 读书笔记(二)----------一个简单的servlet容器

app1 (建议读者在看本章之前,先看how tomcat works 读书笔记(一)----------一个简单的web服务器 http://blog.csdn.net/dlf123321/article/details/39378157) 回顾我们上一章,我们开发了一个最最简单的web服务器,它可以使用户访问服务器内的静态资源.当然这是远远不够的,在这一节里,我们就试着让服务器在能相应静态资源的基础上继续支持servlet. servlet接口 javax.servlet.Servlet接口

how tomcat works 读书笔记四 tomcat的默认连接器

其实在第三章,就已经有了连接器的样子了,不过那只是一个学习工具,在这一章我们会开始分析tomcat4里面的默认连接器. 连接器 Tomcat连接器必须满足以下几个要求 1 实现org.apache.cataline.Connector接口 2 负责创建实现了org.apache.cataline.Request接口的request对象 3 负责创建实现了org.apache.cataline.Response接口的response对象 这里默认的连接器的原理很简单,就是等待http请求,创建re

《How Tomcat Works》读书笔记(二)

<How Tomcat Works>读书笔记(二) 这是<How Tomcat Works>第一二章的读书笔记.第一张主要写了一个静态资源处理的web服务器,第二章加了对servlet的处理. 1. 概述 1.1 架构 HttpServer:表示Http服务器,与客户端通信,处理Http请求. StaticResourceProcessor:对静态资源请求进行处理. ServletProcessor:对Servlet资源请求进行处理. Request:表示Http请求,实现了Ser

How Tomcat works — 四、tomcat启动(3)

上一节说到StandardService负责启动其子组件:container和connector,不过注意,是有先后顺序的,先启动container,再启动connector,这一节先来看看container. 目录 Pipeline和Vavle StandardEngine类和StandardHost类 StandardContext类 总结 Pipeline和Vavle 在第二节(How Tomcat works — 二.tomcat启动(1))中没有介绍关于Pipeline和Vavle,因

What is Servlet Container

What is Servlet Container Normal Java application’s starting point is Main(public static void main(String args[])) method. When ever java application needs to start, main method of the application should be invoked. But in case of Servlet this is not

How Tomcat Works 2

上一节(How Tomcat Works 1 编写一个简单静态web服务器)编写了一个简单的web服务器,只能处理静态的资源,本节将继续向前迈出一个小步,创建两个不同的servlet容器,能够利用servlet简单的处理动态内容.注意每节的代码都是基于上一节的继续丰富,因此有必要从第一节开始看起. 在编写代码之前,需要先大体了解一下Servlet是什么,方便后面的理解,下面就是一个最简单的Servlet什么也没做: package prymont; import java.io.IOExcept

How Tomcat works — 二、tomcat启动(1)

主要介绍tomcat启动涉及到的一些接口和类. 目录 概述 tomcat包含的组件 Lifecycle Container Connector 总结 概述 tomcat作为一个服务器,它的主要功能就是接收请求——处理请求——返回,如果是我们自己实现一个最简单的服务器,启动一个线程监听某个端口,该端口有数据进来的话就接收数据,然后在启动一个线程去处理接收到的数据并返回.那么启动一个服务器最重要的就是启动一个线程监听某个端口,tomcat也是,只不过tomcat包含很多组件,首先要完成组件的初始化,

How Tomcat Works读书笔记三-------连接器

几个概念 HttpServlet,Servlet Servlet是一个接口,定义了一种网络服务,我们所有的servlet都要实现它(或它的子类) HttpServlet是一个抽象类,它针对的就是http网络服务 当然如果以后再有其他的网络服务,可以再定义一个类,让它实现Servlet即可. HttpServletRequest,ServletRequest 先说ServletRequest接口,它用来传递网络服务的请求,用在Servlet类的service方法. 那么HttpServletReq