如何自己动手写一个 Tomcat

最近刚好工作中有遇到一个安全问题,IE 在 URL 地址栏中输入 < 这种特殊字符的时候,将会抛出异常打印异常堆栈信息,这样你的系统是不安全的,而且即使你在项目中使用了拦截器、过滤器等进行拦截都没用办法处理。原因是<并没有进入我们自己的web应用而是在中间间层tomcat容器就已经报错了。原因tomcat8.5.30之后拦截特殊字符解决办法

我们的 Web 应用是运行在 Tomcat 中的,请求必须是先经过 Tomcat 的。Tomcat 你也可以理解为是一个应用。他需要提供几个常见的服务。

  • 提供 Socket 连接

位于网络上,并且要支持 TCP/IP 等协议,肯定是需要基于 Socket 的连接的。

  • 进行请求的转发

一个 Tomcat 可以为多个 Web 应用提供服务,所以 Tomcat 可以把 URL 下发到不同的 Web 应用中去。

  • 需要把请求和响应封装成 request/response 对象

话不多说,上手写程序…

  • 创建项目
    我们这边就只创建简单的java工程,没有使用maven去构建。
    如何自己动手写一个 Tomcat_第1张图片
    如何自己动手写一个 Tomcat_第2张图片
    结构就是这么简单如何自己动手写一个 Tomcat_第3张图片如何自己动手写一个 Tomcat_第4张图片

我们先实现构建好处理请求和响应的类,iRequest 与 iResponse 。

IRequest.java

package com.company;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * tomcat 中的请求接收类
 *
 * @author qiuweijie
 * @since 2020-05-05
 */
public class IRequest {

    // 请求路径
   private String url;
   // 请求方法
    private String method;

    // 读取输入字节流,封装成字符串格式的请求内容
    public IRequest(InputStream inputStream) throws IOException{
        String httpRequest = "";

        byte[] httpRequestBytes = new byte[1024];

        int length =0;

        if ((length = inputStream.read(httpRequestBytes)) > 0) {
            httpRequest = new String(httpRequestBytes, 0 ,length);
        }

        // HTTP 请求协议:首行的内容依次为请求方法、请求路径以及请求协议及其对应版本号
        // GET / HTTP/1.1
        String httpHead = httpRequest.split("\n")[0]; // 取出 HTTP 请求协议的首行
        System.out.println(httpHead);
        method = httpHead.split("\\s")[0];              // 按照空格进行分割,第一个是请求的方法
        url = httpHead.split("\\s")[1];                 // 按照空格进行分割,第二个是请求的路径
        System.out.println(this.toString());
    }

    public String getUrl() {
        return url;
    }

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

    public String getMethod() {
        return method;
    }

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

    @Override
    public String toString() {
        return "IRequest{" +
                "url='" + url + '\'' +
                ", method='" + method + '\'' +
                '}';
    }
}

IResponse.java

package com.company;

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

/**
 * tomcat 中的请求接收类
 * @since 2020-05-05
 * @author qiuweijie
 */
public class IResponse {

    private OutputStream outputStream;
    public IResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    //将文本转换为字节流
    public void write(String content) throws IOException {
        StringBuffer httpResponse = new StringBuffer();
        httpResponse.append("HTTP/1.1 200 OK\n")      //按照HTTP响应报文的格式写入
                .append("Content-Type:text/html\n")
                .append("\r\n")
                .append("")
                .append(content)          //将页面内容写入
                .append("");
        outputStream.write(httpResponse.toString().getBytes());      //将文本转为字节流
        outputStream.close();
    }
}

IServlet .java

package com.company;

/**
 * 抽象的 servlet 类,提供 servlet 常见的三种方法
 */
public abstract class IServlet {
    public void service(IRequest iRequest, IResponse iResponse){
        if(iRequest.getMethod().equalsIgnoreCase("POST")){
            doPost(iRequest,iResponse);
        }else if(iRequest.getMethod().equalsIgnoreCase("GET")){
            doGet(iRequest, iResponse);
        }
    }

    public void doGet(IRequest iRequest, IResponse iResponse) {

    }

    public void doPost(IRequest iRequest, IResponse iResponse) {

    }
}

HelloServlet .java

package com.company;

import java.io.IOException;

public class HelloServlet extends IServlet {

    @Override
    public void doGet(IRequest iRequest, IResponse iResponse) {
        try {
            iResponse.write("get hello servlet");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(IRequest iRequest, IResponse iResponse) {
        try{
            iResponse.write("post hello servlet");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

ServletMapping .java

package com.company;

/**
 * 类似于 web.xml 中的 servlet 标签
 * 存放名字 对应 url 对应类
 */
public class ServletMapping {
    private String servletName;
    private String url;
    private String clazz;

    public ServletMapping(String servletName, String url, String clazz) {
        this.servletName = servletName;
        this.url = url;
        this.clazz = clazz;
    }

    public String getServletName() {
        return servletName;
    }

    public void setServletName(String servletName) {
        this.servletName = servletName;
    }

    public String getUrl() {
        return url;
    }

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

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }
}

ServletMappingConfig .java

package com.company;

import java.util.ArrayList;
import java.util.List;

/**
 * 类似于 web.xml 把一个一个 servlet 标签注册进来
 */
public class ServletMappingConfig {
    public static List<ServletMapping> servletMappingList = new ArrayList<>();

    static {
        servletMappingList.add(new ServletMapping("index", "/index", "com.company.HelloServlet"));
    }
}

ITomcat .java

package com.company;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * 启动 tomcat
 */
public class ITomcat {
    private Integer port = 8888;    // 定义socket连接端口
    private Map<String, String> urlServletMapping = new HashMap<>();    // 存储 url 和 对应的类

    public ITomcat(Integer port) {
        this.port = port;
    }

    public void start(){
        initServletMapping();
        try{
            ServerSocket serverSocket = null;
            serverSocket = new ServerSocket(port);
            while (true){
                Socket socket = serverSocket.accept();
                InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream();
                IRequest iRequest = new IRequest(inputStream);
                IResponse iResponse = new IResponse(outputStream);
                dispatch(iRequest, iResponse);
                socket.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void dispatch(IRequest iRequest, IResponse iResponse) {
        String clazz = urlServletMapping.get(iRequest.getUrl());

        try {
            Class<IServlet> iServletClass = (Class<IServlet>) Class.forName(clazz);
            IServlet iServlet = iServletClass.newInstance();
            iServlet.service(iRequest, iResponse);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void initServletMapping() {
        for (ServletMapping servletMapping: ServletMappingConfig.servletMappingList){
            urlServletMapping.put(servletMapping.getUrl(), servletMapping.getClazz());
        }
    }

    public static void main(String[] args) {
        ITomcat iTomcat = new ITomcat(8888);
        iTomcat.start();
    }
}

测试结果如下:
如何自己动手写一个 Tomcat_第5张图片
现在我们再倒退一下:
先启动 ITomcat 的 main 方法,监听 socket 协议的 8888 窗口.
start 方法里已经做了名称、路径、处理类映射。
如何自己动手写一个 Tomcat_第6张图片
如何自己动手写一个 Tomcat_第7张图片
如何自己动手写一个 Tomcat_第8张图片

页面访问 http://localhost:8888/index
将会通过类反射机制找到相应的处理类
如何自己动手写一个 Tomcat_第9张图片

最后找到处理类的 doget 方法
如何自己动手写一个 Tomcat_第10张图片
调用 response的write 方法
如何自己动手写一个 Tomcat_第11张图片

源码地址:https://github.com/weijieqiu/iStudy

你可能感兴趣的:(Web开发,Servlet)