目录

手写Tomcat实现基本功能

手写Tomcat:实现基本功能

首先,Tomcat是一个软件,所有的项目都能在Tomcat上加载运行,Tomcat最核心的就是Servlet集合,本身就是HashMap。Tomcat需要支持Servlet,所以有servlet底层的资源:HttpServlet抽象类、HttpRequest和HttpResponse,否则我们无法新建Servlet。

这样我们就可以在webapps写项目了,一个项目有两大资源:servlet资源和静态资源,servlet本身是java类,我们让它调用doGet和doPost方法,必须继承HttpServlet,同时也需要@WebServlet注解,那么在Tomcat中就必须要有@WebServlet注解的实现,如果没有@WebServlet,我们就无法拿到相应的注解。这样我们就能成功搭建起Servlet资源。

如果servlet集合在Tomcat启动之前实现,也就是main()方法之前,需要使用static代码块去实现;如果servlet集合在Tomcat启动之后实现,也就是main()方法之后,再去加载servlet容器。

当Http请求打过来之后,先打到socket上,处理Http请求,其本身就是连接器,从请求上能获取到请求路径和请求方法。先获取到请求路径,判断servlet容器中是否含有相同路径,如果有,再获取请求方法,判断是doGet还是doPost。

https://i-blog.csdnimg.cn/direct/38bee0cbb008475099caffa4b1505a88.png

一、Servlet资源准备

1、Servlet接口:

  • Servlet接口的作用: 实现Servlet生命周期 ,定义init()、service(request,response)和destroy()方法。
  • 接口中的方法全都是抽象方法,只定义不实现,方法头默认为public abstract。
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;

public interface Servlet {

    void init();

    void service(HttpServletRequest request, HttpServletResponse response) throws Exception;

    void destroy();
}

2、GenericServlet抽象类

  • GenericServlet抽象类实现Servlet接口中的 init()方法destroy()方法

为什么GenericServlet类是抽象类?

我们知道当一个普通类继承一个接口时,需要对接口中的所有方法进行实现,但这里我们不实现Servlet接口中的service(request,response)方法,因此我们需要把GenericServlet类变成抽象类,因为抽象类不必实现接口中的全部方法。

public abstract class GenericServlet implements Servlet {
   
    public void init(){
        System.out.println("初始化成功");

    }

    public void destroy(){
        System.out.println("销毁成功");

    }
}

3、HttpServlet抽象类

  • HttpServlet抽象类实现Servlet接口中的 service(request,response) 方法

为什么HttpServlet类也是抽象类?

之所以HttpServlet类也是抽象类,是因为普通类中必须对方法进行实现,而在HttpServlet类中,实现service(request,response)方法的逻辑为:如果接收到GET请求,那么执行doGet方法;如果接收到POST请求,那么执行doPost方法。在此我们只需要对doGet和doPost方法进行定义不需要实现,所以HttpServlet只有成为抽象类,才不必实现doGet和doPost方法。

import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;

public abstract class HttpServlet  extends GenericServlet {
    //实现Servlet生命周期的service方法
    public void service(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //如果接收到GET请求,那么执行doGet方法
        if(request.getMethod().equals("GET")){
            doGet(request,response);
        }
        //如果接收到POST请求,那么执行doPost方法
        else if(request.getMethod().equals("POST")){
            doPost(request,response);
        }
    }

    protected abstract void doGet(HttpServletRequest request,HttpServletResponse response) throws Exception;

    protected abstract void doPost(HttpServletRequest request,HttpServletResponse response)throws Exception;
}

4、HttpRequest类

  • HttpRequest类实现 请求路径请求方法 的获取与输出。
public class HttpServletRequest {

    private String path;
    private String method;

    public String getPath(){
        return path;
    };

    public void setPath(String path){
        this.path = path;
    }

    public String getMethod(){
        return method;
    }

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

5、HttpResponse类

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class HttpServletResponse {

    private OutputStream outputStream;

    public HttpServletResponse(OutputStream outputStream){
        this.outputStream = outputStream;
    }

    public void writeServlet(String context) throws IOException{
        outputStream.write(context.getBytes());
    }
}

二、注解

  • @Retention():表示该注解的作用阶段。阶段分三种:源代码阶段、类对象阶段和运行时阶段。
  • @Target():表示该注解的作用范围。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//作用阶段
@Retention(RetentionPolicy.RUNTIME)//作用在运行阶段

//作用范围:类、方法......
@Target(ElementType.TYPE)//作用在类上

public @interface WebServlet {
    String urlMapping() default "";//自定义的servlet路径
}

三、工具类

1、SearchClassUtil类

SearchUtil类的功能:扫描com.qcby.webapps包下的文件,获取全路径名

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/*
* 扫描com.qcby.webapps包下的文件,获取全路径名
* */
public class SearchClassUtil {
    public static List<String> classPaths = new ArrayList<String>();

    public static List<String> searchClass(){
        //需要扫描的包名
        String basePack = "com.qcby.webapps";
        //将获取到的包名转换为路径
        String classPath = SearchClassUtil.class.getResource("/").getPath();
        basePack =  basePack.replace(".", File.separator);
        String searchPath = classPath + basePack;
        doPath(new File(searchPath),classPath);
        //这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
        return classPaths;
    }

    /**
     * 该方法会得到所有的类,将类的绝对路径写入到classPaths中
     * @param file
     */
    private static void doPath(File file,String classpath) {
        if (file.isDirectory()) {//文件夹
            //文件夹我们就递归
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1,classpath);
            }
        } else {//标准文件
            //标准文件我们就判断是否是class文件
            if (file.getName().endsWith(".class")) {
                String path = file.getPath().replace(classpath.replace("/","\\").
                                replaceFirst("\\\\",""),"").replace("\\",".").
                        replace(".class","");
                //如果是class文件我们就放入我们的集合中。
                classPaths.add(path);
            }
        }
    }

    public static void main(String[] args) {
        List<String> classes = SearchClassUtil.searchClass();
        for (String s: classes) {
            System.out.println(s);
        }
    }
}

2、ResponseUtil类

public class ResponseUtil {
    public  static  final String responseHeader200 = "HTTP/1.1 200 \r\n"+
            "Content-Type:text/html; charset=utf-8 \r\n"+"\r\n";

    public static String getResponseHeader404(){
        return "HTTP/1.1 404 \r\n"+
                "Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + "404";
    }

    public static String getResponseHeader200(String context){
        return "HTTP/1.1 200 \r\n"+
                "Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + context;
    }
}

四、Servlet类

1、LoginServlet类

import com.qcby.Servlet.HttpServlet;
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;
import com.qcby.Util.ResponseUtil;
import com.qcby.Util.WebServlet;

import java.io.IOException;

@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("我是LoginServlet下的doGet方法");
        response.writeServlet(ResponseUtil.getResponseHeader200("hello"));
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("我是LoginServlet下的doPost方法");
    }
}

五、ServletConfigMapping类

ServletConfigMapping类是Servlet容器,是Tomcat中最核心的部分,其本身是一个HashMap,其功能为:将路径和对象写入Servlet容器中。

import com.qcby.Servlet.HttpServlet;
import com.qcby.Util.SearchClassUtil;
import com.qcby.Util.WebServlet;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ServletConfigMapping {

    //创建servlet容器
    public static Map<String, HttpServlet> servletMap = new HashMap<>();

    //将Path和对象写入servlet容器当中
    //采用static代码块 在启动tomcat之前实现
    static{
        List<String> classesPath = SearchClassUtil.searchClass();
        for(String path:classesPath){
            try{
                //加载类
                Class clazz = Class.forName(path);
                //获取注解
                WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
                HttpServlet servlet = (HttpServlet) clazz.newInstance();
                servletMap.put(webServlet.urlMapping(),servlet);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

六、MyTomcat

import com.qcby.Servlet.HttpServlet;
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;

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

public class MyTomcat {

    static HttpServletRequest request = new HttpServletRequest();


    public static void main(String[] args) throws Exception {
        //ServletConfigMapping.init(); // tomcat启动,但是还没有监听之前,将信息加载入servlet容器当中


        //1.创建serversocket对象,持续监听8080端口
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true){
            //accept():阻塞监听  ,当代码执行到这一样,如果没有数据到来,那么循环阻塞在这里。如果有数据到来,就继续向下执行
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
            HttpServletResponse response = new HttpServletResponse(outputStream);

            int count = 0;
            while (count == 0){
                count = inputStream.available();
            }

            byte[] bytes = new byte[count];
            inputStream.read(bytes);
            String Context = new String(bytes);
            System.out.println(Context);

            //解析数据
            if(Context.equals("")){
                System.out.println("你输入了一个空请求");
            }else {
                String firstLine = Context.split("\\n")[0];
                request.setMethod(firstLine.split("\\s")[0]);
                request.setPath(firstLine.split("\\s")[1]);
            }

            //如果我们访问的路径在servlet容器当中存在
            if(ServletConfigMapping.servletMap.containsKey(request.getPath())){
                HttpServlet servlet = ServletConfigMapping.servletMap.get(request.getPath());
                servlet.service(request,response);
            }
        }
    }
}