手动实现 Tomcat 底层机制+ 自己设Servlet最终版本V3

实现任务阶段 3- 处理 Servlet

分析+代码实现

● 分析示意图

在这里插入图片描述

WyxRequestHandler

1.这里我们可以对客户端/浏览器进行IO编程/交互

2.新增业务逻辑
(1) 判断uri是什么资源 => 工具方法
(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
(3) 因为目前并没有起到tomcat, 不是一个标准的web项目
(4) 把读取的静态资源放到 target/classes/cal.html
过滤,拦截 , 权限等待 => Handler… => 分发

  1. 有了filter机制,可以理解再调用servlet之前,先匹配filter
package com.wyxdu.tomcat.handler;


import com.wyxdu.tomcat.WyxTomcatV3;
import com.wyxdu.tomcat.http.WyxRequest;
import com.wyxdu.tomcat.http.WyxResponse;

import com.wyxdu.tomcat.servlet.WyxHttpServlet;
import com.wyxdu.tomcat.utils.WebUtils;


import java.io.*;
import java.net.Socket;


public class WyxRequestHandler implements Runnable {
    
    

    //定义Socket
    private Socket socket = null;

    public WyxRequestHandler(Socket socket) {
    
    
        this.socket = socket;
    }

    @Override
    public void run() {
    
    

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
    
    
       
            WyxRequest wyxRequest = new WyxRequest(socket.getInputStream());
     

            //这里我们可以同wyxResponse对象,返回数据给浏览器/客户端
            WyxResponse wyxResponse = new WyxResponse(socket.getOutputStream());

     
            //1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            String uri = wyxRequest.getUri();



            //=====================新增业务逻辑==========
            //(1) 判断uri是什么资源 => 工具方法
            //(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
            //(3) 因为目前并没有起到tomcat, 不是一个标准的web项目
            //(4) 把读取的静态资源放到 target/classes/cal.html
            //过滤,拦截 , 权限等待 => Handler.... => 分发
            if(WebUtils.isHtml(uri)) {
    
    //就是静态页面
                String content = WebUtils.readHtml(uri.substring(1));
                content = wyxResponse.respHeader + content;
                //得到outputstream , 返回信息(静态页面)给浏览器
                OutputStream outputStream = wyxResponse.getOutputStream();
                outputStream.write(content.getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }


            //有了filter机制,可以理解再调用servlet之前,先匹配filter
            //1. 根据request对象封装的uri
            //2. 到 filterUrlMapping 去匹配
            //3. 如果匹配上就调用 filterMapping 对应的filer对象doFilter()
            //4. 如果没有匹配上,就直接走我们后的servlet/jsp/html.

            String servletName = WyxTomcatV3.servletUrlMapping.get(uri);
            if (servletName == null) {
    
    
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 WyxCalServlet
            WyxHttpServlet wyxHttpServlet =
                    WyxTomcatV3.servletMapping.get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost
            if (wyxHttpServlet != null) {
    
    //得到
                wyxHttpServlet.service(wyxRequest, wyxResponse);
            } else {
    
    
                //没有这个servlet , 返回404的提示信息
                String resp = wyxResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = wyxResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

            
            socket.close();


        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //最后一定确保socket要关闭
            if (socket != null) {
    
    
                try {
    
    
                    socket.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

    }
}

wyxResponse

当我们需要给浏览器返回数据时,可以通过wyxResponse 的输出流完成

public class wyxResponse {
    
    

    private OutputStream outputStream = null;

    //写一个http的响应头 => 先死后活
    public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    //在创建 wyxResponse 对象时,传入的outputStream是和Socket关联的
    public wyxResponse(OutputStream outputStream) {
    
    
        this.outputStream = outputStream;
    }
    //当我们需要给浏览器返回数据时,可以通过wyxResponse 的输出流完成
    //
    public OutputStream getOutputStream() {
    
    
        return outputStream;
    }
}

wyxRequest

  1. wyxRequest 作用是封装http请求的数据 get /WyxCalServlet?num1=10&num2=30
  2. 比如 method(get) 、 uri(/wyxCalServlet) 、 还有参数列表 (num1=10&num2=30)
  3. wyxRequest 作用就等价原生的servlet 中的HttpServletRequest
  4. 一会走代码
  5. 这里考虑的是GET请求
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;

/**
 * 1. wyxRequest 作用是封装http请求的数据
 * get /WyxCalServlet?num1=10&num2=30
 * 2. 比如 method(get) 、 uri(/wyxCalServlet) 、 还有参数列表 (num1=10&num2=30)
 * 3. wyxRequest 作用就等价原生的servlet 中的HttpServletRequest
 * 4. 一会走代码
 * 5. 这里考虑的是GET请求
 */
public class wyxRequest {
    
    

    private String method;
    private String uri;
    //存放参数列表 参数名-参数值 => HashMap
    private HashMap<String, String> parametersMapping =
            new HashMap<>();
    private InputStream inputStream = null;


    //构造器=> 对http请求进行封装 => 可以写的代码封装成方法
    //inputStream 是和 对应http请求的socket关联
    public wyxRequest(InputStream inputStream) {
    
    
        this.inputStream = inputStream;
        //完成对http请求数据的封装..
        encapHttpRequest();
    }

    /**
     * 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取
     */
    private void encapHttpRequest() {
    
    
        System.out.println("wyxRequest init()");
        try {
    
    
            //inputstream -> BufferedReader
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            //读取第一行
            /**
             * GET /wyxCalServlet?num1=10&num2=30 HTTP/1.1
             * Host: localhost:8080
             * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Fi
             */
            String requestLine = bufferedReader.readLine();
            //GET - /WyxCalServlet?num1=10&num2=30 - HTTP/1.1
            String[] requestLineArr = requestLine.split(" ");
            //得到method
            method = requestLineArr[0];
            //解析得到 /WyxCalServlet
            //1. 先看看uri 有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) {
    
     //说明没有参数列表
                uri = requestLineArr[1];
            } else {
    
    
                //[0,index)
                uri = requestLineArr[1].substring(0, index);
                //获取参数列表->parametersMapping
                //parameters => num1=10&num2=30
                String parameters = requestLineArr[1].substring(index + 1);
                //num1=10 , num2=30 .... parametersPair= ["num1=10","num2=30" ]
                String[] parametersPair = parameters.split("&");
                //防止用户提交时 /wyxCalServlet?
                if (null != parametersPair && !"".equals(parametersPair)) {
    
    
                    //再次分割 parameterPair = num1=10
                    for (String parameterPair : parametersPair) {
    
    
                        //parameterVal ["num1", "10"]
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2) {
    
    
                            //放入到 parametersMapping
                            parametersMapping.put(parameterVal[0], parameterVal[1]);
                        }
                    }
                }
            }
            //这里不能关闭流 inputStream 和 socket关联
            //inputStream.close();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    //request对象有一个特别重要方法
    public String getParameter(String name) {
    
    
        if (parametersMapping.containsKey(name)) {
    
    
            return parametersMapping.get(name);
        } else {
    
    
            return "";
        }
    }

    public String getMethod() {
    
    
        return method;
    }

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

    public String getUri() {
    
    
        return uri;
    }

    public void setUri(String uri) {
    
    
        this.uri = uri;
    }

    @Override
    public String toString() {
    
    
        return "wyxRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", parametersMapping=" + parametersMapping +
                '}';
    }
}

wyxServlet接口

public interface wyxServlet {
    
    

    void init() throws Exception;

    void service(wyxRequest request, wyxResponse response) throws IOException;

    void destroy();
}

wyxHttpServlet

这里我们使用的了模板设计模式
让wyxHttpServlet 子类 wyxCalServlet 实现

public abstract class wyxHttpServlet implements wyxServlet {
    
    

    @Override
    public void service(wyxRequest request, wyxResponse response) throws IOException {
    
    
        //说明 equalsIgnoreCase 比较字符串内容是相同,不区别大小写
        if("GET".equalsIgnoreCase(request.getMethod())) {
    
    
            //这里会有动态绑定
            this.doGet(request,response);
        } else if("POST".equalsIgnoreCase(request.getMethod())) {
    
    
            this.doPost(request,response);
        }
    }

    //这里我们使用的了模板设计模式
    //让wyxHttpServlet 子类 wyxCalServlet 实现

    public abstract void doGet(wyxRequest request, wyxResponse response);
    public abstract void doPost(wyxRequest request, wyxResponse response);
}

wyxCalServlet

返回计算结果给浏览器

public class wyxCalServlet extends wyxHttpServlet {
    
    
    @Override
    public void doGet(wyxRequest request, wyxResponse response) {
    
    
        //java基础的 OOP 的动态绑定机制..
        //写业务代码,完成计算任务
        int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
        int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);

        int sum = num1 + num2;

        //返回计算结果给浏览器
        //outputStream 和 当前的socket关联
        OutputStream outputStream = response.getOutputStream();
        String respMes = wyxResponse.respHeader
                + "<h1>" + num1 + " + " + num2 + " = " + sum + " wyxTomcatV3 - 反射+xml创建</h1>";
        try {
    
    
            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(wyxRequest request, wyxResponse response) {
    
    
        this.doGet(request, response);
    }

    @Override
    public void init() throws Exception {
    
    

    }

    @Override
    public void destroy() {
    
    

    }
}

WebUtils

1.将字符串转成数字方法
2.判断uri是不是html文件
3.根据文件名来读取该文件->String

public class WebUtils {
    
    

    //将字符串转成数字方法
    public static int parseInt(String strNum, int defaultVal) {
    
    
        try {
    
    
            return Integer.parseInt(strNum);
        } catch (NumberFormatException e) {
    
    
            System.out.println(strNum + " 不能转成数字");
        }
        return defaultVal;
    }

    //判断uri是不是html文件
    public static boolean isHtml(String uri) {
    
    

        return uri.endsWith(".html");
    }

    //根据文件名来读取该文件->String
    public static String readHtml(String filename) {
    
    
        String path = com.wyxdu.utils.WebUtils.class.getResource("/").getPath();
        StringBuilder stringBuilder = new StringBuilder();

        try {
    
    
            BufferedReader bufferedReader = new BufferedReader(new FileReader(path + filename));
            String buf = "";
            while ((buf = bufferedReader.readLine()) != null) {
    
    
                stringBuilder.append(buf);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        return stringBuilder.toString();
    }
}

wyxTomcatV3

第3版的Tomcat, 实现通过xml+反射来初始化容器

代码里面的容器图

在这里插入图片描述

package com.wyxdu.tomcat;

import com.wyxdu.tomcat.handler.wyxRequestHandler;
import com.wyxdu.tomcat.servlet.wyxHttpServlet;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.Filter;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 第3版的Tomcat, 实现通过xml+反射来初始化容器
 */
public class wyxTomcatV3 {
    
    

    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例

    public static final ConcurrentHashMap<String, wyxHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    //你可以这里理解session, tomcat还维护一个容器
    public static final ConcurrentHashMap<String, HttpSession>
            sessionMapping = new ConcurrentHashMap<>();
    

    //你可以这里理解filter, tomcat还维护了filter的容器
    public static final ConcurrentHashMap<String, String>
            filterUrlMapping = new ConcurrentHashMap<>();

    public static final ConcurrentHashMap<String, Filter>
            filterMapping = new ConcurrentHashMap<>();
    
    public static void main(String[] args) {
    
    
        wyxTomcatV3 wyxTomcatV3 = new wyxTomcatV3();
        wyxTomcatV3.init();
        //启动wyxtomcat容器
        wyxTomcatV3.run();
    }


    //启动WyxTomcatV3容器
    public void run() {
    
    

        try {
    
    
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====wyxtomcatv3在8080监听======");
            while (!serverSocket.isClosed()) {
    
    
                Socket socket = serverSocket.accept();
                wyxRequestHandler wyxRequestHandler =
                        new wyxRequestHandler(socket);
                new Thread(wyxRequestHandler).start();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

    }

    //直接对两个容器进行初始化
    public void init() {
    
    
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = wyxTomcatV3.class.getResource("/").getPath();
        //System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        
        try {
    
    
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤到 servlet servlet-mapping
            for (Element element : elements) {
    
    
                if ("servlet".equalsIgnoreCase(element.getName())) {
    
    
                    //这是一个servlet配置
                    //System.out.println("发现 servlet");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (wyxHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
    
    
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");

                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        //验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

猜你喜欢

转载自blog.csdn.net/apple_67445472/article/details/131772541