Bootstrap

DIY-Tomcat part 3 实现对动态资源的请求

实现ServletRequest

package connector;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

/*
GET /index.html HTTP/1.1
        Host: localhost:8888
        Connection: keep-alive
        Cache-Control: max-age=0
        Upgrade-Insecure-Requests: 1
        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
*/

public class Request implements ServletRequest {

  private static final int BUFFER_SIZE = 1024;

  private InputStream input;
  private String uri;

  public Request(InputStream input) {
    this.input = input;
  }

  public String getRequestURI() {
    return uri;
  }

  public void parse() {
    int length = 0;
    byte[] buffer = new byte[BUFFER_SIZE];
    try {
      length = input.read(buffer);
    } catch (IOException e) {
      e.printStackTrace();
    }

    StringBuilder request = new StringBuilder();
    for (int j=0; j<length; j++) {
      request.append((char)buffer[j]);
    }
    uri = parseUri(request.toString());
  }

  private String parseUri(String s) {
    int index1, index2;
    index1 = s.indexOf(' ');
    if (index1 != -1) {
      index2 = s.indexOf(' ', index1 + 1);
      if (index2 > index1) {
        return s.substring(index1 + 1, index2);
      }
    }
    return "";
  }

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

  @Override
  public Enumeration 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 String getContentType() {
    return null;
  }

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

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

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

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

  @Override
  public Map 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 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;
  }
}
  • 主要是为了实现ServletRequest 接口

实现ServletResponse

package connector;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.*;
import java.util.Locale;

/*
HTTP/1.1 200 OK
 */
public class Response implements ServletResponse {

  private static final int BUFFER_SIZE = 1024;

  Request request;
  OutputStream output;

  public Response(OutputStream output) {
    this.output = output;
  }

  public void setRequest(Request request) {
    this.request = request;
  }

  public void sendStaticResource() throws IOException {
    File file = new File(ConnectorUtils.WEB_ROOT, request.getRequestURI());
    try {
      write(file, HttpStatus.SC_OK);
    } catch (IOException e) {
      write(new File(ConnectorUtils.WEB_ROOT, "404.html"), HttpStatus.SC_NOT_FOUND);
    }
  }

  private void write(File resource, HttpStatus status) throws IOException {
    try (FileInputStream fis = new FileInputStream(resource)) {
      output.write(ConnectorUtils.renderStatus(status).getBytes());
      byte[] buffer = new byte[BUFFER_SIZE];
      int length = 0;
      while ((length = fis.read(buffer, 0, BUFFER_SIZE)) != -1) {
        output.write(buffer, 0, length);
      }
    }
  }

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

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

  @Override
  public PrintWriter getWriter() throws IOException {
    PrintWriter writer = new PrintWriter(output, true);
    return writer;
  }

  @Override
  public void setContentLength(int i) {

  }

  @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;
  }
}
  • 也是主要为了实现ServletRequest 接口
  • 实现getWriter()方法

实现Servlet

import connector.ConnectorUtils;
import connector.HttpStatus;
import connector.Request;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeServlet implements Servlet {

  @Override
  public void init(ServletConfig servletConfig) throws ServletException {

  }

  @Override
  public ServletConfig getServletConfig() {
    return null;
  }

  @Override
  public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    PrintWriter out = servletResponse.getWriter();
    out.println(ConnectorUtils.renderStatus(HttpStatus.SC_OK));
    out.println("What time is it now?");
    out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
          .format(new Date()));
  }

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

  @Override
  public void destroy() {

  }
}

  • service实时返回当前时间的具体值

实现ServletProcessor

package processor;

import connector.*;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class ServletProcessor {

  URLClassLoader getServletLoader() throws MalformedURLException {
    File webroot = new File(ConnectorUtils.WEB_ROOT);
    URL webrootUrl = webroot.toURI().toURL();
    return new URLClassLoader(new URL[]{webrootUrl});
  }

  Servlet getServlet(URLClassLoader loader, Request request) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    /*
     /servlet/TimeServlet
    */
    String uri = request.getRequestURI();
    String servletName = uri.substring(uri.lastIndexOf("/") + 1);

    Class servletClass = loader.loadClass(servletName);
    Servlet servlet = (Servlet) servletClass.newInstance();
    return servlet;
  }

  public void process(Request request, Response response) throws IOException {
  //加载loader, 以获得具体servlet的上级目录
    URLClassLoader loader = getServletLoader();
    //通过request中的uri和loader中的上级目录加载对应的具体servlet类处理请求
    Servlet servlet = getServlet(loader, request);
    //用加载的servlet类处理请求
    servlet.service(request, response);
}

主要方法功能解释

  1. Class servletClass = loader.loadClass(servletName);
    loader会从webrootUrl的路径下尝试加载名字为servletName的类,这是实现请求动态资源的关键
  2. 用loader和request加载具体的servlet,再用该servlet处理请求,输出内容

改进Connector

改进Connector使其能够处理来自客户端对动态资源的访问请求

      if (request.getRequestURI().startsWith("/servlet/")) {
        ServletProcessor processor = new ServletProcessor();
        processor.process(request, response);
      } else {
        StaticProcessor processor = new StaticProcessor();
        processor.process(request, response);
      }
  • 如果请求是以/servlet/开头的,代表是对动态资源的请求,则将该请求交由ServletProcessor处理
  • 如果不是以/servlet/开头的,代表是对静态资源的请求,则交由StaticProcessor处理

测试

IDE客户端请求测试

    public static void main(String[] args)throws Exception{
        Socket socket = new Socket("localhost", 8888);
        OutputStream output = socket.getOutputStream();
        output.write("GET /servlet/TimeServlet HTTP/1.1".getBytes());
        socket.shutdownOutput();

        InputStream input = socket.getInputStream();
        byte[] buffer = new byte[2048];
        int length = input.read(buffer);
        StringBuilder response = new StringBuilder();
        for (int j=0; j<length; j++) {
            response.append((char)buffer[j]);
        }
        System.out.println(response.toString());
        socket.shutdownInput();

        socket.close();
    }

web浏览器测试

在这里插入图片描述
上述两种测试方法均能返回正确结果

;