手写Mini版Tomcat服务器

手写Mini版Tomcat服务器

原创: sean9468 程序员文章集锦
原文地址

基本原理

Tomcat服务器是servlet容器,主要用于部署javaWeb项目。Tomcat主要实现了监听socket,获得输入流,输出流,解析http包创建request,response,调用servlet.doFilter和servlet.service 函数填充response信息,写入客户端输出流。

核心代码

详细代码点击原文查看gitee地址。

核心代码:

启动类:BootStrap

连接器:HttpConnector

http处理类:HttpProcessor

HttpRequest

HttpResponse

门面类:RequesFacade

门面类:ResponseFacade 

测试代码:Servlet和静态HTML文件。

PrimitiveServle  

test.html

Bootstrap

启动类就是一个主函数,整个Web项目的入口,平常开发用的war包是没有项目入口的,其实是在Tomcat的启动类中。即使用spring boot开发也是包含了tomcat的依赖,打包为jar包。也是同样的道理。

public class BootStrap {
    public static void main(String[] args) {
        HttpConnector connector = new HttpConnector();
        connector.start();
    }
}

连接器

连接器HttpConnector主要目的就是监听端口,这里采用同步阻塞。监听代码是新建一个线程负责。所有的客户端请求都是这同一个线程处理,效率不是最好的,但对于稳定小流量是可以了。关于线程和IO模型也是一个重要的技术面。这里不过多阐述。

public class HttpConnector implements Runnable {
    boolean stopped;
    private String scheme = "http";

    public String getScheme() {
        return scheme;
    }

    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            serverSocket = new ServerSocket(port, 100, InetAddress.getByName("127.0.0.1"));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        while (!stopped) {
            Socket socket = null;
            try {
                socket = serverSocket.accept();

            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
            HttpProcessor processor = new HttpProcessor();
            processor.process(socket);
        }
    }
}

Http处理类

这是一个核心类,在Tomcat中也属于连接器的职责范围,主要是生成Request 和Response。其中Request中的Header Cookie RequestLine RequestPara参数都一一解析。这部分会对HTTP协议了解的更深入。

process方法

将客户端的InputStream 进一步封装为SocketInputStream ,创建HttpRequest、解析HTTP头信息、创建HttpResponse;这里主要是GET请求。
根据解析出的URI判断是要Servlet处理还是静态文件处理。Servlet会调用我们的写好的测试Servlet代码,静态资源会处理本地静态html文件,如果没有找到就会返回404。

public void process(Socket socket) {
    SocketInputStream input = null;
    OutputStream output = null;
    try {
        input = new SocketInputStream(socket.getInputStream(), 2048);
        output = socket.getOutputStream();
        request = new HttpRequest(input);
        response = new HttpResponse(output);
        response.setRequest(request);
        response.setHeader("Server", "sean servlet container");
        parseRequest(input, output);
        parseHeaders(input);

        if (request.getRequestURI().startsWith("/servlet/")) {
            ServletProcessor processor = new ServletProcessor();
            processor.process(request, response);
        } else {
            StaticResourceProcessor processor = new StaticResourceProcessor();
            processor.process(request, response);
        }
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ServletException e) {
        e.printStackTrace();
    }
}

解析RequestLine

将客户端的InputStream 进一步封装为SocketInputStream ,该类实现了解析HTTP首行的HTTP method 、HTTP URI、HTTP protocol 和version 即封装的RequestLine。然后赋值给Request中的method,URI ,protocol。
注意:解析RequestLine 要优先于解析Header。


        input.readRequestLine(requestLine);
        String method = new String(requestLine.method, 0, requestLine.methodEnd);
        String uri;
    String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
    if (method.length() < 1) {
        throw new ServletException("Missing Http request method");
    } else if (requestLine.uriEnd < 1) {
        throw new ServletException("Missing Http request URI");

    }

    int question = requestLine.indexOf("?");
    if (question >= 0) {
        request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
        uri = new String(requestLine.uri, 0, question);
    } else {
        request.setQueryString(null);
        uri = new String(requestLine.uri, 0, requestLine.uriEnd);
    }
    if (!uri.startsWith("/")) {
        int pos = uri.indexOf("://");
        if (pos == -1) {
            uri = "";
        } else {
            uri = uri.substring(pos);
        }
    }
    request.setUri(uri);
    request.setMethod(method);
    request.setProtocol(protocol);

}
解析Headers

Headers信息只有其实是name-value键值对,根据不同name可以提取不同的元素,比如cookies,content-length ,user-agent等。 提取Header是一个循环,每次循环都会创建一个Header直到读取完毕为止。

private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
    while (true) {
        httpHeader = new HttpHeader();
        input.readHeader(httpHeader);
        if (httpHeader.nameEnd == 0) {
            if (httpHeader.valueEnd == 0) {
                return;
            } else {
                throw new ServletException("parseHeaders exceptions");
            }
        }
        request.addHeader(httpHeader);
        if (httpHeader.name.equals("cookie")) {
            Cookie cookies[] = RequestUtil.parseCookieHeader(new String(httpHeader.value));
            for (int i = 0; i < cookies.length; i++) {
                request.addCookie(cookies[i]);
            }
        } else if (httpHeader.name.equals("content-length")) {
            int n = Integer.parseInt(new String(httpHeader.value));
            request.setContentLength(n);
        }
    }
}

Servlet处理类

该Servlet处理类根据URI请求的名字加载Servlet Class 、实例化、调用service函数。 关于service传参其实传递的是Request和Response的门面类。主要目的是为了安全避免不必要的接口暴漏给Servlet业务程序员。

public class ServletProcessor {
    public void process(HttpRequest request, HttpResponse response) {
        String uri = request.getRequestURI();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        URLClassLoader loader = null;
        try {
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File(Constants.WEB_ROOT);
            String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
            loader = new URLClassLoader(urls);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    Class myClass = null;
    try {
        servletName = "tomcat.step3.servlet." + servletName;

        myClass = loader.loadClass(servletName);

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    Servlet servlet = null;
    try {
        servlet = (Servlet) myClass.newInstance();
        RequestFacade requestFacade = new RequestFacade(request);
        ResponseFacade responseFacade = new ResponseFacade(response);
        servlet.service(requestFacade, responseFacade);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (ServletException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

HttpResponse

这里将处理静态文件的代码放到了HttpResponse中,sendStaticResource方法中。

public void sendStaticResource() throws IOException {
    byte[] bytes = new byte[BUFFER_SIZE];
    try {
        File file = new File(HttpServer.WEB_ROOT, httpRequest.getRequestURI());
        System.out.println(file.getAbsolutePath());
        if (file.exists()) {
            System.out.println("文件存在");
            InputStreamReader reader = new InputStreamReader(new FileInputStream(file));
            BufferedReader reader1 = new BufferedReader(reader);

            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
        String line = null;
        while ((line = reader1.readLine()) != null) {
            System.out.println(line);
            writer.write(line);
        }
        writer.flush();
        writer.close();
        reader.close();
        reader1.close();
    } else {
        String erroMessage = "HTTP/1.1 404 Fie Not Found\r\n" +
                "Content-Type: text/html\r\n" +
                "Content-length:23\r\n" +
                "\r\n" +
                "<h1>File Not Found</h1>";
        output.write(erroMessage.getBytes());
    }
} catch (Exception e) {
    System.out.println(e.toString());
} finally {

    }
}

调试运行

请求PrimitiveServlet

在这里插入图片描述
请求test.html
在这里插入图片描述
请求test-notfound.html

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值