实现一个简易的tomcat

实现一个简易的tomcat

总结

学习到了servlet的接受和分发请求的流程。

步骤

步骤一

先创建一个SpringBoot。这个不难。然后配置了一些日志信息就没了。

pom.xml
<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
          <artifactId>spring-boot-starter-logging</artifactId>
          <groupId>org.springframework.boot</groupId>
        </exclusion>
      </exclusions>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>

    <dependency>
      <artifactId>spring-boot-starter-log4j2</artifactId>
      <groupId>org.springframework.boot</groupId>
    </dependency>
      
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.3.4.RELEASE</version>
      </plugin>
    </plugins>
  </build>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="zzb-tomcat">

  <Properties>
    <Property name="LOGGING_PATTERN">
      %d{yyyy-MM-dd HH:mm:ss.SSS} {%-5level} [%t] {%c{1.}.%M(%L)}: %msg%n
    </Property>
  </Properties>

  <Appenders>
    <!-- info level logger console -->
    <Console name="STDOUT" target="SYSTEM_OUT">
      <PatternLayout pattern="${LOGGING_PATTERN}"/>
    </Console>

    <!-- error level logger console -->
    <Console name="STDERR" target="SYSTEM_ERR">
      <PatternLayout pattern="${LOGGING_PATTERN}"/>
      <ThresholdFilter level="error"/>
    </Console>

    <!-- logger async append -->
    <Async name="ASYNC">
      <AppenderRef ref="STDOUT"/>
      <AppenderRef ref="STDERR"/>
    </Async>
  </Appenders>

  <Loggers>
    <Root level="INFO">
      <AppenderRef ref="ASYNC"/>
    </Root>

    <Logger name="com.tomcat.demo" level="DEBUG"/>

  </Loggers>
</Configuration>

步骤二

接下来都是代码

Request请求
@Log4j2
@Data
public class MyRequest {

    /**
     * 请求地址
     */
    private String url;

    /**
     * 请求方法
     */
    private String method;
    
    public MyRequest(InputStream inputStream) {
        StringBuffer httpRequest = new StringBuffer();
        byte[] httpRequestByte = new byte[1024];
        int length = 0;

        try {
            if ((length = inputStream.read(httpRequestByte)) > 0) {
                httpRequest.append(new String(httpRequestByte, 0, length));
            }
            log.info("httpRequest = [{}]", httpRequest);
            if ("".equals(httpRequest.toString())) {
                return;
            }
            
            String httpHead = httpRequest.toString().split("\n")[0];
            this.url = httpHead.split("\\s")[1];
            this.method = httpHead.split("\\s")[0];
            log.info("MyRequests = [{}]", this);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:这里的httpRequest判空的原因是因为我在用PostMan测试post请求时,发现PostMan会先发一次空的无用链接。所以这里为了项目能运行下去所以判空

Response请求
@Data
@AllArgsConstructor
public class MyResponse {
  private OutputStream outputStream;

  public void write (String content) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("HTTP/1.1 200 OK\n")
        .append("Content-type:text/html\n")
        .append("\r\n")
        .append("<html><head><title>Hello World</title></head><body>")
        .append(content)
        .append("</body><html>");
    try {
      outputStream.write(stringBuilder .toString().getBytes());
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        outputStream.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}
写一个父类的servlet,负责判断请求
@Log4j2
public abstract class MyServlet {

  private static final String POST = "POST";
  private static final String GET = "GET";

  /**
   * get方法
   * @param request 请求
   * @param response 回应
   */
  protected abstract void doGet(MyRequest request, MyResponse response);

  /**
   * post方法
   * @param request 请求
   * @param response 回应
   */
  protected abstract void doPost(MyRequest request, MyResponse response);

  public void service(MyRequest request, MyResponse response) {
    if (POST.equalsIgnoreCase(request.getMethod())) {
      doPost(request, response);
    } else if (GET.equalsIgnoreCase(request.getMethod())) {
      doGet(request, response);
    } else {
      log.info("方法不是GET或POST");
    }
  }
}

步骤三

基本类和父类写完了。接下来要写子类servlet(业务)去实现父类了

student类
public class StudentServlet extends MyServlet {

  @Override
  protected void doGet(MyRequest request, MyResponse response) {
    response.write("I am a student ---- GET");
  }

  @Override
  protected void doPost(MyRequest request, MyResponse response) {
    response.write("I am a student ---- POST");
  }
}
teacher类
public class TeacherServlet extends MyServlet {

  @Override
  protected void doGet(MyRequest request, MyResponse response) {
    response.write("I am a teacher ---- GET");
  }

  @Override
  protected void doPost(MyRequest request, MyResponse response) {
    response.write("I am a teacher ---- POST");
  }
}

favicon

public class FaviconServlet extends MyServlet {

  @Override
  protected void doGet(MyRequest request, MyResponse response) {
    response.write("Favicon ---- GET");
  }

  @Override
  protected void doPost(MyRequest request, MyResponse response) {
    response.write("Favicon ---- POST");
  }
}

步骤四

还记得最早开始学servlet的时候,要在xml文件里映射。这里就不做xml的解析了直接写个类存储,然后写个map里映射

servletMapping对象类
@Data
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 class ServletMappingConfig {

  public static List<ServletMapping> servletMappings = new ArrayList<ServletMapping>(16);

  static {
    servletMappings
        .add(new ServletMapping("student", "/student", "com.tomcat.demo.one.StudentServlet"));
    servletMappings
        .add(new ServletMapping("teacher", "/teacher", "com.tomcat.demo.one.TeacherServlet"));
    servletMappings
        .add(new ServletMapping("favicon", "/favicon.ico", "com.tomcat.demo.one.FaviconServlet"));
  }
}

步骤五

初始化配置,分发请求,返回结果
@Log4j2
@Data
@AllArgsConstructor
public class MyTomcat {

  private Integer port;
  private static final Map<String, String> URL_SERVLET_MAPPING = new HashMap<>(16);

  @SuppressWarnings("InfiniteLoopStatement")
  public void start() {
    // 初始化请求映射关系
    initServletMapping();

    try {
      ServerSocket serverSocket = new ServerSocket(port);
      log.info("My Tomcat begin start");

      while (true) {
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        OutputStream outputStream = socket.getOutputStream();
        MyRequest myRequest = new MyRequest(inputStream);
        MyResponse myResponse = new MyResponse(outputStream);

        //分发请求
        dispatch(myRequest, myResponse);
        socket.close();
      }

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

  private void dispatch(MyRequest request, MyResponse response) {
    String clazz = URL_SERVLET_MAPPING.get(request.getUrl());
    
    if (null == clazz) {
      log.debug("找不到{}", request.getUrl());
      return;
    }

    try {
      Class<?> servletClass = Class.forName(clazz);
      MyServlet myServlet = (MyServlet) servletClass.getDeclaredConstructor().newInstance();
      myServlet.service(request, response);
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
      e.printStackTrace();
    }
  }

  private void initServletMapping() {
    ServletMappingConfig.servletMappings
        .forEach(src -> URL_SERVLET_MAPPING.put(src.getUrl(), src.getClazz()));
  }
}
启动类

我们不用SpringBoot的启动类来启动。自己new个类写个Main方法即可。

public class Main {
  public static void main(String[] args) {
    new MyTomcat(8080).start();
  }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值