把Tomcat8的源码画在脑袋里(上)

如何学习源码,对于我来说,拿到一个框架/开源项目,首先思考的是,为什么有人写这个东西,它的出现是为了解决什么样的现实问题。知道了框架/开源项目的前世今生,就更容易融入框架作者的思路里来。

Tomcat对于从事java开发/学习的同学来说实在是太亲切了,每个人都用过,那么,为什么会有tomcat这个东西呢?他的出现解决了程序员们哪些现实问题?当我们运行一个web项目时,我们将项目打成war包,并将war包放到tomcat的webApp文件夹下,启动tomcat,剩下的我们不需要再考虑,交给Tomcat来管理即可。这一篇文章,我们假设没有Tomcat,项目的运行和发布都自己来实现,我们会怎么做?我们绘制一张思路图:

  • 它首先应该能够接受请求,
  • 根据请求的内容,它可以找到对应的web项目的处理者(service)
  • 处理后的结果,它可以返还给请求者

以上三点是我们目前来看最最基本的,也是最最迫切需要的功能。那么我们开始吧。

新建一个JAVA项目,并新建一个DemoTomcat类,在main方法里创建serverSocket用于监听某个端口:


/**
 * @author syy
 * @Title: DemoTomcat
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 14:39
 */
public class DemoTomcat {


    public static void main(String args[]) throws IOException {
        ServerSocket server = new ServerSocket(8080);
        System.out.println("开始监听8080端口");
        while (true) {
            Socket socket = server.accept();
            String content = null;
            byte[] buff = new byte[1024];
            int len;
            if ((len = socket.getInputStream().read(buff)) > 0) {
                content = new String(buff, 0, len);
                System.out.println(content);
            }
        }
    }
}

运行该main方法(main是类的入口),控制台打印出:开始监听8080端口   ,说明监听8080的serverSocket开始工作。

在浏览器中输入地址: http://localhost:8080/myapp?id=23  并访问,查看控制台打印的内容:

GET /myapp?id=23 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

请求已经收到了,并且有丰富的参数,但我们从浏览器看到的是访问一直在转圈圈,什么原因呢?因为我们没有返回响应,所以浏览器一直在等待(转圈圈)。

下面我们为请求增加响应:

 public static void main(String args[]) throws IOException {
        ServerSocket server = new ServerSocket(8080);
        System.out.println("开始监听8080端口");
        while (true) {
            Socket socket = server.accept();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            //响应 hello World
            String response = "Hello World";
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(response.getBytes());
            outputStream.flush();
            outputStream.close();
        }
    }

如上,刷新网页,在谷歌浏览器中发现报错了:ERR_INVALID_HTTP_RESPONSE  怎么回事?代码很简单,步骤也很明白,什么原因呢?别担心,这是因为浏览器访问的请求和响应有一定的格式要求。回顾下上面打印的请求内容可以看出来,请求包含:请求方式(GET/POST),Http协议(HTTP/1.1)HttpHost,Connection,Cache-Control,Upgrade-Insecure-Requests,User-Agent等信息,那么,我们也为响应指定浏览器必须包含的信息(协议、响应的格式):

 public static final String responseHeader="HTTP/1.1 200 \r\n"
            + "Content-Type: text/html\r\n"
            + "\r\n";

    public static void main(String args[]) throws IOException {
                        ......
            //响应
            String response = responseHeader+"Hello World";
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(response.getBytes());
            outputStream.flush();
            outputStream.close();
        }
    }

现在结果正常了。到此,我们已经具备了基本的请求和响应的功能。有了基本的请求响应链路,我们开始思考,我们的请求如何传参呢?如何根据Url将请求分配给对应的处理者?http请求的方式有post和get,我们如何区分呢?,带着问题,我们对思路图进行完善:

上面我们知道,请求/响应包含很多信息,包括方法,协议,内容,类型等,我们将请求和响应封装成对象,方便使用。

新建两个类文件,分别为Request和Response:

/**
 * @author syy
 * @Title: Request
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 17:00
 */
public class Request {
    private String method;
    private String url;

    public Request(InputStream inputStream) throws IOException {
        //请求的第一行类似于这样 GET /myapp?id=23 HTTP/1.1,从这里可以分辨出是post还是get,也可以得到请求的完整地址
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String[] methodAndUrl = bufferedReader.readLine().split(" ");
        this.method= methodAndUrl[0];
        this.url=methodAndUrl[1];
    }


    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}
/**
 * @author syy
 * @Title: Response
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 17:13
 */
public class Response {
    public OutputStream outputStream;

    public static final String responseHeader="HTTP/1.1 200 \r\n"
            + "Content-Type: text/html\r\n"
            + "\r\n";

    public Response(OutputStream outputStream) throws IOException {
        this.outputStream= outputStream;
    }
}

修改DemoTomcat的代码:

/**
 * @author syy
 * @Title: DemoTomcat
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 14:39
 */
public class DemoTomcat {

    public static void main(String args[]) throws IOException {
        ServerSocket server = new ServerSocket(8080);
        System.out.println("开始监听8080端口");
        while (true) {
            Socket socket = server.accept();
            Request  request=new Request(socket.getInputStream());
            Response response=new Response(socket.getOutputStream());
        }
    }
}

新建第一个处理请求的severlet,新建类文件命名为FirstSeverlet:

import java.io.OutputStream;

/**
 * @author syy
 * @Title: FirstServlet
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 17:24
 */
public class FirstServlet {

    public void service(Request request, Response response) {

        //判断是调用doget 还是 dopost
        if ("get".equalsIgnoreCase(request.getMethod())) {
            this.doGet(request, response);
        } else {
            this.doPost(request, response);
        }
    }

    public void doGet(Request request, Response response) {
        try {
            OutputStream outputStream = response.outputStream;
            String res = Response.responseHeader + "Get Request ->FirstServlet!!";
            outputStream.write(res.getBytes());
            outputStream.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    public void doPost(Request request, Response response) {
        try {
            OutputStream outputStream = response.outputStream;
            String res = Response.responseHeader + "Post Request ->FirstServlet!!";
            outputStream.write(res.getBytes());
            outputStream.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

 

为了丰富servlet,我们拷贝一份FirstServlet,命名为SecondServlet,并修改doPost和doGet:

import java.io.OutputStream;

/**
 * @author syy
 * @Title: SecondServlet 
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 17:24
 */
public class SecondServlet {

    public void service(Request request, Response response) {

        //判断是调用doget 还是 dopost
        if ("get".equalsIgnoreCase(request.getMethod())) {
            this.doGet(request, response);
        } else {
            this.doPost(request, response);
        }
    }

    public void doGet(Request request, Response response) {
        try {
            OutputStream outputStream = response.outputStream;
            String res = Response.responseHeader + " Get Request ->SecondServlet!";
            outputStream.write(res.getBytes());
            outputStream.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    public void doPost(Request request, Response response) {
        try {
            OutputStream outputStream = response.outputStream;
            String res = Response.responseHeader + "Post Request ->SecondServlet!!";
            outputStream.write(res.getBytes());
            outputStream.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

为了调用servlet方便,我们新建一个ProcessRequest类,用于判断具体调用哪个servlet:

import java.io.OutputStream;
import java.net.Socket;

/**
 * @author syy
 * @Title: ProcessRequest
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 17:36
 */
public class ProcessRequest extends Thread {

    private Socket socket;
    private Request request;
    private Response response;

    public ProcessRequest(Socket socket, Request request, Response response) {
        this.socket = socket;
        this.request = request;
        this.response = response;
    }

    @Override
    public void run() {
        try {
            if (request.getUrl().contains("first")) {
                FirstServlet firstSevelet = new FirstServlet();

                if ("GET".equalsIgnoreCase(request.getMethod())) {
                    firstSevelet.doGet(request, response);
                } else {
                    firstSevelet.doPost(request, response);
                }
            } else if (request.getUrl().contains("second")) {
                SecondServlet secondSevelet = new SecondServlet();
                if ("GET".equalsIgnoreCase(request.getMethod())) {
                    secondSevelet.doGet(request, response);
                } else {
                    secondSevelet.doPost(request, response);
                }
            } else {
                String res = Response.responseHeader + "servlet not found";
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write(res.getBytes("UTF-8"));
                outputStream.flush();
                outputStream.close();
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
修改DemoTomcat的代码:

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

/**
 * @author syy
 * @Title: DemoTomcat
 * @ProjectName DemoTomcat
 * @Description: TODO
 * @date 2019/4/11 14:39
 */
public class DemoTomcat {

    public static void main(String args[]) throws IOException {
        ServerSocket server = new ServerSocket(8080);
        System.out.println("开始监听8080端口");
        while (true) {
            Socket socket = server.accept();
            Request  request=new Request(socket.getInputStream());
            Response response=new Response(socket.getOutputStream());
            Thread thread = new ProcessRequest(socket,request,response);
            thread.start();
        }
    }
}

运行,在浏览器中输入http://localhost:8080/first 结果如下:

运行,在浏览器中输入结果如下:http://localhost:8080/second 结果如下:

运行,在浏览器中输入http://localhost:8080/third结果如下: 

可见,请求已经被我们根据url分发给不同的servlet处理了。

上面代码的ProcessRequest里写死了servlet的调用关系,并不利于servlet的扩展,我们应该有一个配置文件,配置文件里配置了有哪些servlet,每个servlet处理哪些对应的请求,随着业务的增长或者webapp的增多,需要增加处理的servlet的时候,我们只需要修改配置文件即可

我们新建一个servlet.yml文件来当作servlet的配置文件,url中是first则交给FirstServlet处理,second则交给SecondServlet处理:

加载yml文件的代码也很简单,篇幅限制,我已将代码打包上传,需要的同学可以下载运行。点击这里下载

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大大大大碗面

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值