照着大佬视频敲了一遍......

Tomcat是什么

是一个Http服务器+Servlet容器,直接屏蔽了应用层的协议解析和网络通信细节,为我们提供了标准的Request和Responsse对象,从而只需要关注业务逻辑。

启动类:

package com.ljw.MyTomcat;

import jakarta.servlet.Servlet;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class MyTomcat {


    // tomcat的上下文,封装每一个app的上下文
    private Map<String,Context> contextMap = new HashMap<>();


    public Map<String, Context> getContextMap() {
        return contextMap;
    }
    public static void main(String[] args) {
        MyTomcat tomcat = new MyTomcat();
        // 部署一个tomcat中的每一个app
        tomcat.deployApps();
        // 启动
        tomcat.start();
    }


    private void start() {

        try {
            // 使用线程池:如果只是使用单个线程,那么在处理一个请求时,如果有其他请求过来,将会被阻塞到第一个请求被处理完
            // 主线程负责建立链接,交由线程池进行处理请求
            // 实际tomcat并不是这样的简单处理
            ExecutorService service = Executors.newFixedThreadPool(20);
            ServerSocket socket = new ServerSocket(8090);
            while (true) {
                Socket accept = socket.accept();
                service.execute(new SocketHandler(accept,this));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void deployApps() {
        File file = new File(System.getProperty("user.dir"), "webapps");
        for (String app : file.list()) {
            deployApp(file,app);
        }
    }



    private void deployApp(File webapps, String app) {
        // 有哪些app
        File appDirectory = new File(webapps, app);
        File classesDirectory = new File(appDirectory, "classes");
        List<File> files = getAllFilePath(classesDirectory);
        for (File file : files) {
            // 项目的servlet上下文:封装每一个项目的servlet
            Context context = new Context();
            String name = file.getPath();
            // 去掉工作目录前缀
            name = name.replace(classesDirectory.getPath()+"\\","");
            // 去掉.class后缀
            name = name.replace(".class","");
            // 将目录格式com\ljw 转换成 com.ljw 的形式
            name = name.replace("\\",".");
            try {
                WebAppClassLoader classLoader = new WebAppClassLoader(new URL[]{classesDirectory.toURL()});
                Class<?> servletClass = classLoader.loadClass(name);
                // 判断 servletClass 的父类是否是 HttpServlet
                if (HttpServlet.class.isAssignableFrom(servletClass)){
                    // 是否被@WebServlet 标记
                    if (servletClass.isAnnotationPresent(WebServlet.class)) {
                        // 获取路径的值
                        WebServlet annotation = servletClass.getAnnotation(WebServlet.class);
                        String[] urlPatterns = annotation.urlPatterns();
                        // 每一个路径对应一个servlet
                        for (String urlPattern : urlPatterns) {
                            context.addUrlPatternMapping(urlPattern,(Servlet) servletClass.newInstance());
                        }
                    }
                    contextMap.put(app,context);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 递归获取目录下所有文件
     * @param srcFile
     * @return
     */
    public List<File> getAllFilePath(File srcFile){
        List<File> list = new ArrayList<>();
        File[] files = srcFile.listFiles();
        if (files != null){
            for (File file : files) {
                if (file.isDirectory()){
                    list.addAll(getAllFilePath(file));
                }else {
                    list.add(file);
                }
            }
        }
        return list;
    }


}

协议解析、业务处理:

package com.ljw.MyTomcat;

import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;

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


/**
 * socket连接处理
 */
public class SocketHandler implements Runnable {

    private Socket socket;

    public SocketHandler(Socket socket) {
        this.socket = socket;
    }
    public SocketHandler(Socket socket,MyTomcat tomcat) {
        this.socket = socket;
        this.tomcat = tomcat;
    }

    @Override
    public void run() {
        handleSocket(socket);
    }

    private MyTomcat tomcat= new MyTomcat();
    /**
     * http协议格式
     * 请求方法 空格 URL 空格 协议版本 回车符 换行符
     * 头部字段名 : 值 回车符 换行符 -|
     * ...                        |- 请求头
     * 头部字段名 : 值 回车符 换行符 -|
     * 回车符 换行符
     * 请求数据
     *
     * @param socket
     */
    private void handleSocket(Socket socket) {
        // 处理socket连接
        try {
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            inputStream.read(bytes);
            // 解析字节内容
            for (byte b : bytes) {
                /**
                 * 打印内容:
                 * GET /test HTTP/1.1
                 * Host: localhost:8090
                 * Connection: keep-alive
                 * sec-ch-ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
                 * sec-ch-ua-mobile: ?0
                 * sec-ch-ua-platform: "Windows"
                 * Upgrade-Insecure-Requests: 1
                 * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
                 * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,q = 0.8, application / signed - exchange;v = b3;q = 0.7
                 * ...
                 */
//                System.out.print((char) b);
            }

            int pos = 0;
            int begin = 0,end = 0;
            // 解析请求类型
            for (; pos < bytes.length; pos++,end++) {
                if (bytes[pos] == ' ') break;
            }
            StringBuilder method = new StringBuilder();
            for (;begin<end;begin++){
                method.append((char) bytes[begin]);
            }
//            System.out.println(method);

            // 解析URL
            pos++;begin++;end++;
            for (; pos < bytes.length; pos++,end++) {
                if (bytes[pos] == ' ') break;
            }
            StringBuilder url = new StringBuilder();
            for (;begin<end;begin++){
                url.append((char) bytes[begin]);
            }
//            System.out.println(url);

            // 解析协议版本
            pos++;begin++;end++;
            for (; pos < bytes.length; pos++,end++) {
                if (bytes[pos] == '\r') break;
            }
            StringBuilder protocl = new StringBuilder();
            for (;begin<end;begin++){
                protocl.append((char) bytes[begin]);
            }
//            System.out.println(protocl);

            // 构建请求
            Request request = new Request(method.toString(),url.toString(),protocl.toString(),socket);

            // 匹配Servlet:doGet doPost
//            TestServlet testServlet = new TestServlet();
            Response response = new Response(request);
//            testServlet.service(request,response);
            String requestUrl = request.getRequestURI().toString();
            // 去掉请求的第一个斜杠:/hello/test
            requestUrl = requestUrl.substring(1);// hello/test
            String[] split = requestUrl.split("/");
            String appName = split[0];
            // 获取项目的上下文
            Context context = tomcat.getContextMap().get(appName);
            if (context == null){
                System.out.println(appName);
            }
            if (split.length > 1){
                Servlet servlet = context.getByUrlPattern(split[1]);// /test
                if (servlet == null){
                    // 默认返回处理
                    DefaultServlet defaultServlet = new DefaultServlet();
                    defaultServlet.service(request,response);
                    response.complete();
                }else {
                    // 业务处理
                    servlet.service(request,response);
                    // 发送响应
                    response.complete();
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
    }


}

请求对象抽象类:

package com.ljw.MyTomcat;

import jakarta.servlet.*;
import jakarta.servlet.http.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;


public class AbstractRequest implements HttpServletRequest {
    @Override
    public String getAuthType() {
        return null;
    }

    @Override
    public Cookie[] getCookies() {
        return new Cookie[0];
    }

    @Override
    public long getDateHeader(String s) {
        return 0;
    }

    @Override
    public String getHeader(String s) {
        return null;
    }

    @Override
    public Enumeration<String> getHeaders(String s) {
        return null;
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return null;
    }

    @Override
    public int getIntHeader(String s) {
        return 0;
    }

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

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

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

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

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

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

    @Override
    public boolean isUserInRole(String s) {
        return false;
    }

    @Override
    public Principal getUserPrincipal() {
        return null;
    }

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

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

    @Override
    public StringBuffer getRequestURL() {
        return null;
    }

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

    @Override
    public HttpSession getSession(boolean b) {
        return null;
    }

    @Override
    public HttpSession getSession() {
        return null;
    }

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

    @Override
    public boolean isRequestedSessionIdValid() {
        return false;
    }

    @Override
    public boolean isRequestedSessionIdFromCookie() {
        return false;
    }

    @Override
    public boolean isRequestedSessionIdFromURL() {
        return false;
    }

    @Override
    public boolean isRequestedSessionIdFromUrl() {
        return false;
    }

    @Override
    public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
        return false;
    }

    @Override
    public void login(String s, String s1) throws ServletException {

    }

    @Override
    public void logout() throws ServletException {

    }

    @Override
    public Collection<Part> getParts() throws IOException, ServletException {
        return null;
    }

    @Override
    public Part getPart(String s) throws IOException, ServletException {
        return null;
    }

    @Override
    public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {
        return null;
    }

    @Override
    public Object getAttribute(String s) {
        return null;
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return null;
    }

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

    @Override
    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {

    }

    @Override
    public int getContentLength() {
        return 0;
    }

    @Override
    public long getContentLengthLong() {
        return 0;
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return null;
    }

    @Override
    public String getParameter(String s) {
        return null;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return null;
    }

    @Override
    public String[] getParameterValues(String s) {
        return new String[0];
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return null;
    }

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

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

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

    @Override
    public int getServerPort() {
        return 0;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return null;
    }

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

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

    @Override
    public void setAttribute(String s, Object o) {

    }

    @Override
    public void removeAttribute(String s) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }

    @Override
    public Enumeration<Locale> getLocales() {
        return null;
    }

    @Override
    public boolean isSecure() {
        return false;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String s) {
        return null;
    }

    @Override
    public String getRealPath(String s) {
        return null;
    }

    @Override
    public int getRemotePort() {
        return 0;
    }

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

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

    @Override
    public int getLocalPort() {
        return 0;
    }

    @Override
    public ServletContext getServletContext() {
        return null;
    }

    @Override
    public AsyncContext startAsync() throws IllegalStateException {
        return null;
    }

    @Override
    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
        return null;
    }


   @Override
    public boolean isAsyncStarted() {
        return false;
    }

    @Override
    public boolean isAsyncSupported() {
        return false;
    }

    @Override
    public AsyncContext getAsyncContext() {
        return null;
    }

    @Override
    public DispatcherType getDispatcherType() {
        return null;
    }
}

请求对象:


package com.ljw.MyTomcat;

import java.net.Socket;

public class Request extends AbstractRequest {
    private String method;
    private String url;
    private String protocol;

    // 需要绑定当前请求的socket连接对象,用于获取outputsream进行响应
    private Socket socket;

    public Request(String method, String url, String protocol,Socket socket) {
        this.method = method;
        this.url = url;
        this.protocol = protocol;
        this.socket = socket;
    }

    public String getMethod() {
        return method;
    }

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

    public String getRequestURI() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }
}

响应抽象类:

package com.ljw.MyTomcat;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Locale;


public class AbstractResponse implements HttpServletResponse {
    @Override
    public void addCookie(Cookie cookie) {

    }

    @Override
    public boolean containsHeader(String s) {
        return false;
    }

    @Override
    public String encodeURL(String s) {
        return null;
    }

    @Override
    public String encodeRedirectURL(String s) {
        return null;
    }

    @Override
    public String encodeUrl(String s) {
        return null;
    }

    @Override
    public String encodeRedirectUrl(String s) {
        return null;
    }

    @Override
    public void sendError(int i, String s) throws IOException {

    }

    @Override
    public void sendError(int i) throws IOException {

    }

    @Override
    public void sendRedirect(String s) throws IOException {

    }

    @Override
    public void setDateHeader(String s, long l) {

    }

    @Override
    public void addDateHeader(String s, long l) {

    }

    @Override
    public void setHeader(String s, String s1) {

    }

    @Override
    public void addHeader(String s, String s1) {

    }

    @Override
    public void setIntHeader(String s, int i) {

    }

    @Override
    public void addIntHeader(String s, int i) {

    }

    @Override
    public void setStatus(int i) {

    }

    @Override
    public void setStatus(int i, String s) {

    }

    @Override
    public int getStatus() {
        return 0;
    }

    @Override
    public String getHeader(String s) {
        return null;
    }

    @Override
    public Collection<String> getHeaders(String s) {
        return null;
    }

    @Override
    public Collection<String> getHeaderNames() {
        return null;
    }

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

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

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return null;
    }

    @Override
    public void setCharacterEncoding(String s) {

    }

    @Override
    public void setContentLength(int i) {

    }

    @Override
    public void setContentLengthLong(long l) {

    }

    @Override
    public void setContentType(String s) {

    }

    @Override
    public void setBufferSize(int i) {

    }

    @Override
    public int getBufferSize() {
        return 0;
    }

    @Override
    public void flushBuffer() throws IOException {

    }

    @Override
    public void resetBuffer() {

    }

    @Override
    public boolean isCommitted() {
        return false;
    }

    @Override
    public void reset() {

    }

    @Override
    public void setLocale(Locale locale) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }

}

响应对象:

package com.ljw.MyTomcat;

import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * 响应对象
 */
public class Response extends AbstractResponse{
    // 状态码
    private int status = 200;
    // 返回信息
    private String message = "OK";
    // 请求头
    private Map<String,String> headers = new HashMap<>();

    // 请求对象
    private Request request;

    private OutputStream socketOutputStream;

    // 响应输出流
    private ResponseServletOutputStream  responseServletOutputStream = new ResponseServletOutputStream();



    @Override
    public int getStatus() {
        return status;
    }

    @Override
    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public void setHeaders(Map<String, String> headers) {
        this.headers = headers;
    }

    @Override
    public void addHeader(String s, String s1) {
        headers.put(s,s1);
    }


    @Override
    public ResponseServletOutputStream getOutputStream() throws IOException {
        return responseServletOutputStream;
    }



    public Response(Request request){
        this.request = request;
        try {
            this.socketOutputStream = request.getSocket().getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public void complete() throws IOException {
        // 响应行
        sendResponseLine();
        // 响应头
        sendHeader();
        // 响应数据
        sendResponseBody();
    }

    private void sendResponseBody() throws IOException {
        socketOutputStream.write(getOutputStream().getBytes());
    }

    private byte SP = ' ';
    private byte CR = '\r';
    private byte LF = '\n';
    private void sendResponseLine() throws IOException {
        socketOutputStream.write(request.getProtocol().getBytes());
        socketOutputStream.write(SP);
        socketOutputStream.write(status);
        socketOutputStream.write(SP);
        socketOutputStream.write(message.getBytes());
        socketOutputStream.write(CR);
        socketOutputStream.write(LF);

    }

    private void sendHeader() throws IOException {
        // 设置响应格式
        if (!headers.containsKey("Content-Length")){
            addHeader("Content-Length",String.valueOf(getOutputStream().getPos()));
        }
        if (!headers.containsKey("Content-Type")){
            addHeader("Content-Type","");
        }

        // 遍历map
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            socketOutputStream.write(key.getBytes());
            socketOutputStream.write(":".getBytes());
            socketOutputStream.write(value.getBytes());
            socketOutputStream.write(CR);
            socketOutputStream.write(LF);
        }
        socketOutputStream.write(CR);
        socketOutputStream.write(LF);
    }
}

响应输出流:

package com.ljw.MyTomcat;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;

import java.io.IOException;

public class ResponseServletOutputStream extends ServletOutputStream {

    // 一个响应可能会写多次,所以需要将响应数据暂存起来
    private byte[] bytes = new byte[1024];
    // 实际的数据长度
    private int pos = 0;

    @Override
    public boolean isReady() {
        return false;
    }

    @Override
    public void setWriteListener(WriteListener writeListener) {

    }

    @Override
    public void write(int b) throws IOException {
        bytes[pos] = (byte) b;
        pos++;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public int getPos() {
        return pos;
    }

    public void setPos(int pos) {
        this.pos = pos;
    }
}

项目app上下文:

package com.ljw.MyTomcat;

import jakarta.servlet.Servlet;

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


/**
 * 每一个项目的上下文
 */
public class Context {
    private String appName;
    private Map<String, Servlet> urlPatternMap = new HashMap<>();

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public void addUrlPatternMapping(String urlPattern,Servlet servlet){
        urlPatternMap.put(urlPattern,servlet);
    }
    public Servlet getByUrlPattern(String urlPattern){
        for (String s : urlPatternMap.keySet()) {
            if (s.contains("/"+urlPattern)){
                return  urlPatternMap.get(s);
            }
        }
        return null;
    }
}

自定义类加载器:

package com.ljw.MyTomcat;

import java.net.URL;
import java.net.URLClassLoader;

public class WebAppClassLoader extends URLClassLoader {
    public WebAppClassLoader(URL[] urls) {
        super(urls);
    }
}

默认处理Servlet:

package com.ljw.MyTomcat;

import jakarta.servlet.http.HttpServlet;


public class DefaultServlet extends HttpServlet {
}

Servlet.class:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ljw.MyTomcat;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(
    urlPatterns = {"/test"}
)
public class TestServlet extends HttpServlet {
    public TestServlet() {
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getMethod());
        resp.setContentType("text/plain;charset=utf-8");
        resp.getOutputStream().write("hello world".getBytes());
    }
}

目录结构: