前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于传统I/O手写Tomcat

基于传统I/O手写Tomcat

作者头像
Tom弹架构
发布2022-02-11 09:28:29
3480
发布2022-02-11 09:28:29
举报
文章被收录于专栏:Tom弹架构Tom弹架构

本文节选自《Netty 4核心原理》

我们知道,Tomcat是基于J2EE规范的Web容器,主要入口是web.xml文件。web.xml文件中主要配置Servlet、Filter、Listener等,而Servlet、Filter、Listener在J2EE中只是抽象的实现,具体业务逻辑由开发者来实现。本章内容,就以最常用的Servlet为例来详细展开。

1 环境准备

1.1 定义GPServlet抽象类

首先,我们创建GPServlet类。我们都知道GPServlet生命周期中最常用的方法是doGet()方法和doPost()方法,而doGet()方法和doPost()方法是service()方法的分支实现,看下面的简易版Servlet源码实现。

代码语言:javascript
复制

package com.tom.tomcat.http;

public abstract class GPServlet {

    public void service(GPRequest request,GPResponse response) throws Exception{

        //由service()方法决定是调用doGet()还是调用doPost()
        if("GET".equalsIgnoreCase(request.getMethod())){
            doGet(request, response);
        }else{
            doPost(request, response);
        }

    }

    public abstract void doGet(GPRequest request,GPResponse response) throws Exception;

    public abstract void doPost(GPRequest request,GPResponse response) throws Exception;

}

从上面的代码中,我们看到,doGet()方法和doPost()方法中有两个参数GPRequest和GPResponse对象,这两个对象是由Web容器创建的,主要是对底层Socket的输入输出的封装。其中GPRequest是对Input的封装,GPResponse是对Output的封装。

1.2 创建用户业务代码

下面基于GPServlet来实现两个业务逻辑FirstServlet和SecondServlet。FirstServlet类的实现代码如下。

代码语言:javascript
复制

package com.tom.tomcat.servlet;

import com.tom.tomcat.http.GPRequest;
import com.tom.tomcat.http.GPResponse;
import com.tom.tomcat.http.GPServlet;

public class FirstServlet extends GPServlet {

    public void doGet(GPRequest request, GPResponse response) throws Exception {
        this.doPost(request, response);
    }

    public void doPost(GPRequest request, GPResponse response) throws Exception {
        response.write("This is First Servlet");
    }

}
SecondServlet类的实现代码如下。
package com.tom.tomcat.servlet;

import com.tom.tomcat.http.GPRequest;
import com.tom.tomcat.http.GPResponse;
import com.tom.tomcat.http.GPServlet;

public class SecondServlet extends GPServlet {

    public void doGet(GPRequest request, GPResponse response) throws Exception {
        this.doPost(request, response);
    }

    public void doPost(GPRequest request, GPResponse response) throws Exception {
        response.write("This is Second Servlet");
    }

}

1.3 完成web.properties配置

为了简化操作,我们用web.properties文件代替web.xml文件,具体内容如下。

代码语言:javascript
复制

servlet.one.url=/firstServlet.do
servlet.one.className=com.tom.tomcat.servlet.FirstServlet

servlet.two.url=/secondServlet.do
servlet.two.className=com.tom.tomcat.servlet.SecondServlet

上述代码分别给两个Servlet配置了/firstServlet.do和/secondServlet.do的URL映射。

2 基于传统I/O手写Tomcat

下面我们来看GPRequest和GPResponse的基本实现。

2.1 创建GPRequest对象

GPRequest主要就是对HTTP的请求头信息进行解析。我们从浏览器发送一个HTTP请求,如在浏览器地址栏中输入 http://localhost:8080 ,后台服务器获取的请求其实就是一串字符串,具体格式如下。

示例:

代码语言:javascript
复制

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

在GPRequest获得输入内容之后,对这一串满足HTTP的字符信息进行解析。我们来看GPRequest简单直接的代码实现。

代码语言:javascript
复制

package com.tom.tomcat.http;

import java.io.InputStream;

/**
 * Created by Tom.
 */
public class GPRequest {

    private String method;
    private String url;

    public GPRequest(InputStream in){
        try {
            //获取HTTP内容
            String content = "";
            byte[] buff = new byte[1024];
            int len = 0;
            if ((len = in.read(buff)) > 0) {
                content = new String(buff,0,len);
            }

            String line = content.split("\\n")[0];
            String [] arr = line.split("\\s");

            this.method = arr[0];
            this.url = arr[1].split("\\?")[0];
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public String getUrl() {
        return url;
    }

    public String getMethod() {
        return method;
    }
}

在上面的代码中,GPRequest主要提供了getUrl()方法和getMethod()方法。输入流InputStream作为GPRequest的构造参数传入,在构造函数中,用字符串切割的方法提取请求方式和URL。

2.2 创建GPResponse对象

接下来看GPResponse的实现,与GPRequest的实现思路类似,就是按照HTTP规范从Output输出格式化的字符串,具体格式如下:

示例:

代码语言:javascript
复制

HTTP/1.1 200 OK
Server: Tomcat
Location: http://localhost:8080
Connection: Keep-Alive
Content-Type: text/html;

This My Servlet

下面来看代码。

代码语言:javascript
复制

package com.tom.tomcat.http;

import java.io.OutputStream;

/**
 * Created by Tom.
 */
public class GPResponse {
    private OutputStream out;
    public GPResponse(OutputStream out){
        this.out = out;
    }

    public void write(String s) throws Exception {
        //输出也要遵循HTTP
        //状态码为200
        StringBuilder sb = new StringBuilder();
                sb.append("HTTP/1.1 200 OK\n")
                .append("Content-Type: text/html;\n")
                .append("\r\n")
                .append(s);
        out.write(sb.toString().getBytes());
    }
}

上面的代码中,输出流OutputStream作为GPResponse的构造参数传入,主要提供了一个write()方法。通过write()方法按照HTTP规范输出字符串。

2.3 创建GPTomcat启动类

前面2.1和2.2两节只是对J2EE规范的再现,接下来就是真正Web容器的实现逻辑,分为三个阶段:初始化阶段、服务就绪阶段、接受请求阶段。第一阶段:初始化阶段,主要是完成对web.xml文件的解析。

代码语言:javascript
复制

package com.tom.tomcat;

import com.tom.tomcat.http.GPRequest;
import com.tom.tomcat.http.GPResponse;
import com.tom.tomcat.http.GPServlet;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Created by Tom.
 */
public class GPTomcat {
    private int port = 8080;
    private ServerSocket server;
    private Map<String,GPServlet> servletMapping = new HashMap<String,GPServlet>();

    private Properties webxml = new Properties();


    private void init(){

        //加载web.xml文件,同时初始化ServletMapping对象
        try{
            String WEB_INF = this.getClass().getResource("/").getPath();
            FileInputStream fis = new FileInputStream(WEB_INF + "web.properties");

            webxml.load(fis);

            for (Object k : webxml.keySet()) {

                String key = k.toString();
                if(key.endsWith(".url")){
                    String servletName = key.replaceAll("\\.url$", "");
                    String url = webxml.getProperty(key);
                    String className = webxml.getProperty(servletName + ".className");
                    //单实例,多线程
                    GPServlet obj = (GPServlet)Class.forName(className).newInstance();
                    servletMapping.put(url, obj);
                }

            }


        }catch(Exception e){
            e.printStackTrace();
        }

    }

}

上面代码中,首先从WEB-INF读取web.properties文件并对其进行解析,然后将URL规则和GPServlet的对应关系保存到servletMapping中。第二阶段:服务就绪阶段,完成ServerSocket的准备工作。在GPTomcat类中增加start()方法。

代码语言:javascript
复制

    public void start(){

        //1.加载配置文件,初始化ServletMapping
        init();

        try {
            server = new ServerSocket(this.port);

            System.out.println("GPTomcat已启动,监听的端口是:" + this.port);

            //2.等待用户请求,用一个死循环来等待用户请求
            while (true) {
                Socket client = server.accept();
                //3.HTTP请求,发送的数据就是字符串——有规律的字符串(HTTP)
                process(client);

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

第三阶段:接受请求阶段,完成每一次请求的处理。在GPTomcat中增加process()方法的实现。

代码语言:javascript
复制

    private void process(Socket client) throws Exception {

        InputStream is = client.getInputStream();
        OutputStream os = client.getOutputStream();

        //4.Request(InputStrean)/Response(OutputStrean)
        GPRequest request = new GPRequest(is);
        GPResponse response = new GPResponse(os);

        //5.从协议内容中获得URL,把相应的Servlet用反射进行实例化
        String url = request.getUrl();

        if(servletMapping.containsKey(url)){
            //6.调用实例化对象的service()方法,执行具体的逻辑doGet()/doPost()方法
            servletMapping.get(url).service(request,response);
        }else{
            response.write("404 - Not Found");
        }


        os.flush();
        os.close();

        is.close();
        client.close();
    }

每次客户端请求过来以后,从servletMapping中获取其对应的Servlet对象,同时实例化GPRequest和GPResponse对象,将GPRequest和GPResponse对象作为参数传入service()方法,最终执行业务逻辑。最后,增加main()方法。

代码语言:javascript
复制

     public static void main(String[] args) {
        new GPTomcat().start();
    }

服务启动后,运行效果如下图所示。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Tom弹架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 环境准备
    • 1.1 定义GPServlet抽象类
      • 1.2 创建用户业务代码
        • 1.3 完成web.properties配置
        • 2 基于传统I/O手写Tomcat
          • 2.1 创建GPRequest对象
            • 2.2 创建GPResponse对象
              • 2.3 创建GPTomcat启动类
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档