模仿tomcat---初识感受

前言

很早就有看到公众号推送的手写简单tomcat的类似文章。一直想模仿着写写,主要感受下人家的思维和思路,今天终于参考别人写的自己手动也写了一下。

整体思路要求

  1. 保证长连接,保持长时间监控端口请求
  2. 封装请求参数信息,其中包括地址映射的servlet,请求地址,请求端口等
  3. 执行请求封装返回信息等

主要代码实现

项目地址:抄袭miniTomcat

  1. 使用Socket做网络连接端口监听,这里一定要注意异常catch掉,否则会断掉服务
/**
     * 启动类
     */
    public void start() {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(PORT);
            //while保持长时间连接
            while (true) {
                //注意这里try catch保证catch到所有错误保证程序不停止
                try {
                    Socket accept = serverSocket.accept();
                    InputStream inputStream = accept.getInputStream();
                    OutputStream outputStream = accept.getOutputStream();
                    Request request = Request.create(inputStream);
                    Response response = Response.create(outputStream);
                    requestMapping.dispatch(request, response);
                    accept.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //保证连接关闭
            if (Objects.nonNull(serverSocket)) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  1. Request封装:analysis方法是主要的解析方法,httpString拿到的字符串就是http请求的request的一整块,通过换行符和空格去拿到对应的需要封装的参数,包括请求参数等(这里只拿了url和请求方式),具体http请求有的参数参考http官方文档
package http;


import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;

/**
 * request封装类
 *
 * @author 50238
 */
public class Request {

    static Request create(InputStream inputStream) {
        return new Request(inputStream);
    }

    /**
     * http
     */
    private String httpString;

    /**
     * 请求方式
     */
    private String method;

    /**
     * 请求地址
     */
    private String url;


    private Request(InputStream inputStream) {
        if (Objects.isNull(inputStream)) {
            throw new NullPointerException("http请求输入错误");
        }
        try {
            byte[] inputByte = new byte[1024];
            inputStream.read(inputByte);
            httpString = new String(inputByte);
            analysis();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析http
     * 主要根据http协议进行解析:header body参数等
     * 这里有一定感觉所谓的协议,不过是按照规矩组成的字符串
     * {参考http协议官方文档@link: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages}
     */
    private void analysis() {
        String[] split = httpString.split("\\n");
        System.out.println(httpString);
        this.method = split[0].split("\\s")[0];
        this.url = split[0].split("\\s")[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;
    }
}

  1. Response封装:这里主要是返回数据封装,即http请求中的Response那一块数据(我这里只返回了一个html字符,Content-type指定返回类型)
package http;

import java.io.IOException;
import java.io.OutputStream;

/**
 * 输出
 *
 * @author 50238
 */
public class Response {
    private OutputStream outputStream;

    static Response create(OutputStream outputStream) {
        return new Response(outputStream);
    }

    private Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    /**
     * 输出内容
     * 这里指定协议,数据类型,返回数据等
     *
     * @param outString
     */
    public void write(String outString) throws IOException {
        String httpResponse = "HTTP/1.1 200 OK\n" +
                "Content-type: text/html\n" +
                "\r\n" +
                "<html><body>" +
                outString +
                "</body></html>";
        outputStream.write(httpResponse.getBytes());
        outputStream.close();
    }
}

  1. servlet定义:MyServlet抽象类定义了通用的get post调用方法,analysis做请求方式的解析调用
package http;

/**
 * 自定义servlet
 *
 * @author 50238
 */
abstract class MyServlet {
    /**
     * get method
     *
     * @param request
     * @param response
     */
    abstract void doGet(Request request, Response response);

    /**
     * post method
     *
     * @param request
     * @param response
     */
    abstract void doPost(Request request, Response response);

    /**
     * 解析请求方式
     * 这里可以进行解析其他请求方式post get put delete等
     * {参考官方文档@link:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/DELETE}
     *
     * @param request
     * @param response
     */
    void analysis(Request request, Response response) {
        String method = request.getMethod().toUpperCase();
        switch (method) {
            case "GET":
                doGet(request, response);
                break;
            case "POST":
                doPost(request, response);
                break;
            default:
                throw new NullPointerException("请求方法不支持");
        }
    }
}


package http;

import java.io.IOException;

/**
 * 自定义Servlet
 *
 * @author 50238
 */
public class Servlet extends MyServlet {

    @Override
    void doGet(Request request, Response response) {
        try {
            response.write("doGet");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    void doPost(Request request, Response response) {
        try {
            response.write("doPost");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  1. 最后地址映射方法(这里只写了一个hello的映射),主要方法init初始化servlet。
package http;

import java.util.HashMap;

/**
 * 地址映射
 *
 * @author 50238
 */
public class RequestMapping {
    /**
     * 地址解析
     * {key:地址,类名}
     */
    private final HashMap<String, String> SERVLET_URL = new HashMap<>(1);

    /**
     * 地址解析
     * {key:地址,实例}
     */
    private final HashMap<String, MyServlet> SERVLETS = new HashMap<>(1);

    {
        //这里提前指定了地址和servlet,等于tomcat解析web.xml
        //这里可以写成注解扫描处理servlet或者解析xml处理,后面有时间写一个吧
        SERVLET_URL.put("/hello", "http.Servlet");
        init();
    }

    /**
     * 初始化
     * 通过反射实例化servlet
     */
    private void init() {
        SERVLET_URL.forEach((k, v) -> {
            try {
                try {
                    SERVLETS.put(k, (MyServlet) Class.forName(v).newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 获取servlet
     *
     * @return
     */
    public void dispatch(Request request, Response response) {
        String url = request.getUrl();
        MyServlet myServlet = SERVLETS.get(url);
        myServlet.analysis(request, response);
    }

}

  1. 启动
import http.MyTomcat;

/**
 * 启动类
 *
 * @author 50238
 */
public class Application {


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

RequestMapping类扩展想法思路:

上面地址解析时写死的,只能请求http//xxxxxxx/hello,如果想丰富的话有两种常用处理方式:

  1. 注解式:通过扫描包将自定义注解@Servlet(可以自己命名)扫描放到容器中,获取必填参数url进行Map(url,servlet)映射
  2. 配置xml方式,解析xml按照格式解析和注解方式一样放入容器

如果想配置过滤器也可以使用servlet相同方式进行处理,放入容器然后地址映射在servlet之前调用即可

个人总结

Http协议:从这个项目中个人感觉所谓的协议就是一串规定格式的串罢了,其实也不是感觉很神秘了。不过看官方文档可以知道很多http可以优化的东西,这里不赘述,作为web开发还是应该知道这些。

Socket:感受到了自己对网络编程这块的缺失,对Socket不熟悉。里面还涉及着大量的东西。

IO:,以前也总是感觉没啥用。在何时起感觉到程序无论什么操作感觉都是在处理IO,让我认识到了IO的重要性。在学习Mysql时很多时候也提到IO对优化的影响,这也是一大片空白需要恶补。

反射:很重要的东西,不管是现在主流的框架或者说tonmcat都是用到了这一特性,也是需要注意学习的。

容器:听着很高大上,可以就理解成一个集合或者Map吧,感觉是一种思想,在之前看Spring、Mybatis的一些东西的时候慢慢的给了我越来越深的印象。

缺失的东西:这个简单的tomcat还没涉及到多线程的请求、session问题、多项目包共享问题、监听器实现、很多参数解析等问题,感觉任重而道远还要学习的东西很多。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

niubility锐

觉得有用的话鼓励鼓励

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

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

打赏作者

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

抵扣说明:

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

余额充值