手写Tomcat的实现代码分享

一.理论知识回顾

如图,首先客户端的请求先要打在网卡上,网卡进行注册端口的操作。

注册完端口请求就会发送给Socket,Socket要打开输入流InputStream,根据输入流解析其中的数据,得到请求地址和请求类型之后,把地址发送给Servlet容器。

Servlet容器根据这个地址找到了对应的Servlet对象之后,找到对应的Servlet对象的地址,解析对象的代码,把对象里的请求类型发送给对象。

对象里的doGet方法此时用request接收,然后根据用户在方法内写下的具体行为,生成response,把response打回到Socket当中,Socket此时打开输出流OutputStream。

然后输出内容,把响应返回给客户端。

如图,这是Servlet执行流程,我们要先让普通类继承HttpServlet这个抽象类,其实,HttpServlet还继承了GenericServlet这个抽象类,而GenericServlet又实现了Servlet接口

二.具体代码

1.文件结构

首先利用Maven创建一个项目。

在“java”文件夹(自带)下创建一个com包,在包下模仿真正的Tomcat的结构,首先直接在com包下创建MyTomcat作为启动器;然后创建config文件夹,里面创建ServletConfigMapp类,用来实现Servlet容器;创建lib文件夹,里面创建Servlet接口,GenericServlet抽象类,HttpServlet抽象类,ServeltRequest接口,HttpServeltRequest类,ServletResponse接口,HttpServletResponse类,WebServlet注解;创建utils文件夹用来放一些工具类;创建webapps.myweb包,下面存放具体实现功能的动态资源(普通类),比如MyFirstServlet类。

在resources文件夹(自带)下创建静态资源,比如login.html。

2.代码

Servlet接口:

public interface Servlet {
    public void init();
    public void service(ServletRequest request,ServletResponse response) throws IOException;
    public void destroy();
}

仿照真实的Sevlet接口,定义好init,service,destroy三个方法。

GenericServlet抽象类:

public abstract class GenericServlet implements Servlet{

    @Override
    public void init() {

    }

    public abstract void service(ServletRequest request, ServletResponse response) throws IOException;

    @Override
    public void destroy() {

    }
}

实现除service之外的方法,不实现的方法要写上abstract关键字

HttpServlet抽象类:

public abstract class HttpServlet extends GenericServlet{
    public abstract void doGet(ServletRequest request, ServletResponse response) throws IOException;
    public abstract void doPost(ServletRequest request, ServletResponse response);
    @Override
    public void service(ServletRequest request, ServletResponse response) throws IOException {
        if(request.getMethod().equals("GET")){
            doGet(request,response);
        }else if(request.getMethod().equals("POST")){
            doPost(request,response);
        }
    }
}

实现service方法,service方法的作用在于根据传来的请求类型,来决定调用doGet还是doPost方法。这里要先把doGet和doPost方法定义好。

ServeltRequest接口:

public interface ServletRequest {
    public String getMethod();
    public String getPath();
    public String setMethod(String method);
    public String setPath(String path);
}

定义好getter和setter方法,用于传来和查询请求类型和请求地址

HttpServeltRequest类:

public class HttpServletRequest implements ServletRequest{
    private String method;
    private String path;

    @Override
    public String getMethod() {
        return method;
    }

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public String setMethod(String method) {
        return this.method = method;
    }

    @Override
    public String setPath(String path) {
        return this.path = path;
    }
}

实现ServeltRequest接口,实现对应的方法。

ServletResponse接口:

public interface ServletResponse {
    public void append(String context) throws IOException;
}

定义好接下来要具体实现的方法。

HttpServletResponse类:

public class HttpServletResponse implements ServletResponse{
    private OutputStream outputStream;

    public void setOutputStream(OutputStream outputStream){
        this.outputStream = outputStream;
    }

    @Override
    public void append(String context) throws IOException {
        outputStream.write(context.getBytes());
    }

    public void returnStatic(String path) throws Exception {
        String resource = FileUtil.getResoucePath(path);
        File file = new File(resource);
        if (file.exists()){
            FileUtil.writeFile(file,outputStream);
        }else {
            System.out.println("404无法找到。。。");
        }
    }
}

实现ServletResponse接口,append方法用于把数据写进输出流形成响应返回到客户端returnnStatic用于针对静态资源把数据写进输出流形成响应返回到客户端。

WebServlet注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {
    String value();
}

注解定义要注意设置运行时生效和针对类生效

Servlet容器ServletConfigMap:

public class ServletConfigMapping {
    public static Map<String, HttpServlet> classMapping = new HashMap<>();
    static {
        try {
            List<String> paths = SearchClassUtil.searchClass("com.webapps.myweb");

            for (String path : paths) {
                try {
                    Class clazz = Class.forName(path);
                    WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
                    HttpServlet servlet = (HttpServlet) clazz.getDeclaredConstructor().newInstance();
                    classMapping.put(webServlet.value(), servlet);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

定义Servlet容器,利用了集合,反射的相关知识。

Tomcat启动器MyTomcat:

public class MyTomcat {
    private static final Integer PORT = 7789;
    private static HttpServletRequest httpServletRequest = new HttpServletRequest();
    private static HttpServletResponse httpServletResponse = new HttpServletResponse();

    public static void start() throws Exception {
        ServerSocket serverSocket = new ServerSocket(PORT);
        while (true){
            Socket socket = serverSocket.accept();
            InputStream stream = socket.getInputStream();
            httpServletResponse.setOutputStream(socket.getOutputStream());
            handler(stream);
        }
    }

    public static void handler(InputStream stream) throws Exception {
        int count = 0;
        while (count == 0){
            count = stream.available();
        }
        byte[] bytes = new byte[count];
        int read = stream.read(bytes);
        String request = new String(bytes,0,read);
        if (request.equals("")){
            System.out.println("请求为空");
        }else {
            String firstLine = request.split("\n")[0];
            String method = firstLine.split("\\s")[0];
            String path = firstLine.split("\\s")[1];
            httpServletRequest.setMethod(method);
            httpServletRequest.setPath(path);
            if(path.equals("")){
                httpServletResponse.append(ResponseUtil.responseHeader404);
            }else if (ServletConfigMapping.classMapping.get(path) != null){
                HttpServlet servlet = ServletConfigMapping.classMapping.get(path);
                servlet.service(httpServletRequest,httpServletResponse);
            }else {
                httpServletResponse.returnStatic(path);
            }
        }

    }

    public static void main(String[] args) throws Exception {
        start();
    }

}

利用Socekt相关知识实现启动器功能。

MyFirstServlet类:

@WebServlet("/first")
public class MyFirstServlet extends HttpServlet {
    @Override
    public void doGet(ServletRequest request, ServletResponse response) throws IOException {
        System.out.println("first地址");
        response.append(ResponseUtil.getResponseHeader200("<h1>这是第一个Servlet</h1>"));
    }

    @Override
    public void doPost(ServletRequest request, ServletResponse response) {

    }
}

继承HttpServlet类,写上WebServlet注解,并实现doGet或doPost方法

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
</body>
</html>

三.效果展示

(这里有一个小bug,输出流输出中文会产生乱码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值