源码学习-tomcat的简单实现(一)

1 代码结构

现在主要想实现一个简单的Tomcat,支持特定请求的解析和静态资源的实现。主要是通过socket来实现的,代码结构如下:
代码结构

2 整体设计

Socket连接接口线程接收到请求后,构造相应的Request和Response,同时构造单个请求对应的处理器,针对不同的资源构造不同的处理器。整体流程如下:

在这里插入图片描述

3 代码介绍

3.1 请求接收处理器

请求接受处理器主要绑定端口,配置线程池,启动端口监听。线程池这边采用默认的配置。主要的过程:

  1. 构造ServerSocket,开始监听端口
  2. 构造Request和Response
  3. 构造Processor来处理单个请求
  4. 将构造的请求丢到线程池中进行处理

相应的代码如下:

package com.sunx.simple.tomcat;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

/**
 * @Author sunx
 * @Description
 * @Date 2019/2/20
 **/
public class AcceptHandler {
    /**
     * 监听的端口
     */
    private int port = 8080;
    /**
     * 是否关闭
     */
    private boolean running = true;
    /**
     * 服务端socket
     */
    private ServerSocket serverSocket;
    /**
     * 线程池
     */
    private ExecutorService pool;

    /**
     * 构造实例对象
     * @return
     */
    public static AcceptHandler newInstance(){
        return new AcceptHandler();
    }
    /**
     * 绑定端口
     * @param port
     * @return
     */
    public AcceptHandler port(int port){
        this.port = port;
        return this;
    }

    public AcceptHandler pool(int threads, ThreadFactory threadFactory){
        pool = Executors.newFixedThreadPool(threads,threadFactory);
        return this;
    }

    /**
     * 启动服务端socket
     * @return
     */
    public AcceptHandler socket(){
        try {
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {
            //抛出异常,显示服务端出现问题,暂不处理
            e.printStackTrace();
        }
        return this;
    }

    /**
     * 启动线程,进行监听接收请求
     * @return
     */
    public AcceptHandler accept(){
        pool.execute(new AcceptTaskThread());
        return this;
    }

    /**
     * 连接请求接受线程
     */
    public class AcceptTaskThread implements Runnable{
        @Override
        public void run() {
            acceptConnection();
        }
    }

    public void acceptConnection(){
        //启动监听
        while(running){
            //接收相应的数据内容
            try {
                System.out.println("start to accept.");
                Socket socket = serverSocket.accept();
                System.out.println("接收到请求:" + socket.toString());
                builderRequestAndResponse(socket);
            } catch (IOException e) {
                //获取连接
                e.printStackTrace();
            }
        }
    }

    public void builderRequestAndResponse(Socket socket) {
        try{
            //包装构造request
            Request request = builderRequestByInputStream(socket.getInputStream());
            //包装构造response
            Response response = builderResponseByOutputStreamAndRequest(socket.getOutputStream(),request);
            //包装当前socket,丢入到线程池中,等待处理
            System.out.println("包装当前socket,丢入到线程池中,等待处理.");
            SocketProcessor processor = SocketProcessor.newInstance(socket,request,response);
            pool.execute(processor);
        }catch (IOException e){
            e.printStackTrace();
            SocketUtils.sendError(socket);
            SocketUtils.close(socket);
        }
    }

    /**
     * 构造请求对象
     * @param inputStream
     * @return
     */
    private Request builderRequestByInputStream(InputStream inputStream) throws IOException{
        System.out.println("构造请求对象");
        Request request = new Request(inputStream);
        request.prepareParseRequest();
        return request;
    }

    /**
     * 构造相应对象
     * @param outputStream
     * @param request
     * @return
     */
    private Response builderResponseByOutputStreamAndRequest(OutputStream outputStream,Request request){
        System.out.println("构造相应对象");
        return new Response(outputStream,request);
    }
}
3.2 构造请求对象

连接过来以后从流中读取出相应的请求头数据,进而解析出相应的数据并填充到Request对象中,Request对象中需要先绑定对应的输入流。

/**
     * 用户请求输入流
     */
    private InputStream inputStream;
    
    public Request(InputStream inputStream) {
        this.inputStream = inputStream;
    }

输入流绑定后,从输入流中读取相应的数据。具体的先从流中读取数据转化为字符串:

/**
     * 解析请求数据
     */
    public String parseRequestLine(){
        StringBuilder temp = new StringBuilder();
        int cache;
        try{
            while ((cache = inputStream.read()) != -1) {
                //读取到第一个\r\n时则说明读取请求行完毕
                if (CARRIAGE_RETURN == cache) {
                    break;
                }
                temp.append((char)cache);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return temp.toString();
    }

请求头相应数据读取出来以后,开始进行解析,将请求头和请求方式等数据,具体的如下:

/**
     * 解析获取到的数据,丰富当前的request数据
     */
    public void prepareParseRequest() throws IOException {
        this.requestStr = parseRequestLine();
        //对数据进行判断
        if (null == requestStr || requestStr.length() == 0) {
            throw new IOException("error request");
        }
        //解析数据,将数据切分出来
        List<String> list = Arrays.asList(requestStr.split("\r\n"));
        if (null == list || list.size() <= 0) {
            throw new RuntimeException("error request");
        }
        //解析请求方式,请求地址,http协议
        parseMethodAndPathFromRequestStr(list.get(0));
        //解析请求头
        parserRequestHeaderFromRequestStr(list);
    }

    /**
     * 解析请求方式
     * 解析请求地址
     * 解析请求协议
     * @param path
     */
    private void parseMethodAndPathFromRequestStr(String path) throws IOException{
        //针对请求头进行处理
        List<String> strs = Arrays.asList(path.split(" "));
        //切分好以后,进行参数结果判断
        if (null == strs || strs.size() != 3) {
            throw new IOException("error request");
        }
        //开始进行相应数据的赋值
        this.requestMethod = strs.get(0);
        this.requestPath = strs.get(1);
        //开始解析路径参数
        //解析参数
    }

    /**
     * 解析请求头
     * @param list
     */
    private void parserRequestHeaderFromRequestStr(List<String> list) throws IOException{
        if(list.size() <= 2){
            return;
        }
        List<String> array = list.subList(1,list.size() - 1);
        //循环遍历,将字符串进行切割后
        for(String item : array){
            String[] strs = item.split(": ");
            if (strs == null || strs.length != 2) {
                throw new IOException("error request");
            }
            header.put(strs[0],strs[1]);
        }
    }
3.3 构造响应对象

响应对象主要需要绑定OutputStream和Request,用于将响应数据返回回去,这里只是做了简单的包装和OutputStream的返回,主要的代码和逻辑如下:

public class Response implements ServletResponse {

    private Request request;
    private OutputStream outputStream;

    public Response(OutputStream outputStream, Request request){
        this.outputStream = outputStream;
        this.request = request;
    }

    public OutputStream getResponseOutputStream(){
        return this.outputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new PrintWriter(this.outputStream,true);
    }
    ......
}
3.4 请求处理器

请求处理器,经过Request中的解析出请求方式,请求地址,请求头等数据后,根据不同的地址和资源进行明确采用不同的资源处理器。Processor继承Runnable,将当前的Processor后丢给线程池进行执行。Processor执行的过程为:

  1. 从Request中获取请求地址,判断地址中是否含有/api,如果含有/api,则走ApiResouceProcessor,否则走StaticResourceProcessor
  2. Api接口的处理,需要找到对应的Servlet,然后实例化后进行执行,如果初步实例化标记的Servlet后,那么可以类似于SpringBoot来进行IOC注入后进行请求分发
  3. 静态资源的获取找到对应的地址后,进行解析展示到前端页面

具体的StaticResourceProcessor代码如下:

/**
     * 静态资源的处理
     */
    public class StaticResourceProcessor {
        /**
         * 开始处理请求
         * @param request
         * @param response
         * @throws IOException
         */
        public void proccess(Request request, Response response) throws Exception {
            //就是上面的那个字符串截取方法
            String uri = request.getRequestPath();
            //获取uri对应的静态资源
            URL ROOT = TomcatServer.class.getClassLoader().getResource(STATIC_RESOURCE_URI);
            File staticResource = new File(ROOT.getPath() + uri);
            if(staticResource.exists() ||staticResource.isFile()){
                response.getResponseOutputStream().write(MessageUtils.responseToByte(200,"OK"));
                write(response.getResponseOutputStream(),staticResource);
            }else{
                staticResource = new File(ROOT.getPath() + "/404.html");
                response.getResponseOutputStream().write(MessageUtils.responseToByte(404,"file not found"));
                write(response.getResponseOutputStream(),staticResource);
            }
        }
        /**
         * 向服务端写入资源数据
         * @param outputStream
         * @param resouce
         */
        private void write(OutputStream outputStream, File resouce){
            try{
                try (FileInputStream fis = new FileInputStream(resouce)) {
                    byte[] cache = new byte[1024];
                    int read;
                    while ((read = fis.read(cache, 0, 1024)) != -1) {
                        outputStream.write(cache, 0, read);
                    }
                    outputStream.flush();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

具体的ApiResouceProcessor代码如下:

/**
     * 接口资源的处理
     */
    public class ApiResouceProcessor{
        /**
         * 开始处理请求
         * @param request
         * @param response
         * @throws IOException
         */
        public void proccess(Request request, Response response) throws Exception {
            //就是上面的那个字符串截取方法
            String uri = request.getRequestPath();
            //找到对应的路径地址对应的servlt
            String clazz = relation.getOrDefault(uri,null);
            if (clazz == null || clazz.length() <= 0 ) {
                throw new RuntimeException("no mapping relation of servlet");
            }
            //实例化该servlet对象
            Class<?> servletClass = this.getClass().getClassLoader().loadClass(clazz);
            //实例化对象
            Servlet servlet = (Servlet)servletClass.newInstance();
            response.getResponseOutputStream().write(MessageUtils.responseToByte(200,"OK"));
            //调用servlet的service方法
            servlet.service(request,response);
        }
    }

在ApiResouceProcessor中,relation为一个映射集合,下面是一个简单的例子:

relation.put("/api/test","com.sunx.simple.tomcat.TestServlet");
3.5 构造自定义Servlet
public class TestServlet implements Servlet {

    @Override
    public void init(ServletConfig config) throws ServletException {

    }

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

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("Start invoke TestServlet ... ");
        response.getWriter().println("Hello Servlet!");
    }

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

    @Override
    public void destroy() {

    }
}

relation的映射关系可以通过注解的方式来实例化自定义的类,用于执行特定的方法,这里不一定是需要实现Servlet.

测试结果如下:
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值