2.1.5. Tomcat实现原理手写实现

2.1.5.1. 简单版

 public static void main(String[] args) {
        ServerSocket ss = null;
        Socket accept = null;
        try {
            ss = new ServerSocket(9998);
            while (true) {
                accept = ss.accept();
                InputStream is = accept.getInputStream();
                byte[] buf = new byte[1024];
                int len = is.read(buf);
                if (len > 0) {
                    System.out.println(new String(buf, 0, len));
                }

                OutputStream outputStream = accept.getOutputStream();
                outputStream.write("<h1>hello word</h1>".getBytes("UTF-8"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                accept.close();
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

访问 http://localhost:9998/ 在后台输出如下报文

GET / HTTP/1.1
Host: localhost:9998
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8
Cookie: bdshare_firstime=1461989447316; pgv_pvid=9782444383; Hm_lvt_7b48cb8bcd42dbe89f577014c9221e43=1473862936,1475644576; _ga=GA1.1.1848041501.1484151117

可以看到上面输入流是客户端请求的信息,输出流是服务端响应的信息。那么进一步抽象。

2.1.5.2. tomcat 简单版

public class Service {
    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket accept = null;
        try {
            ss = new ServerSocket(9999);
            while (true) {
                accept = ss.accept();
                HttpRequest httpRequest = new HttpRequest(accept.getInputStream());
                HttpResponse httpResponse = new HttpResponse(accept.getOutputStream());

                // 判断是否是静态资源
                String uri = httpRequest.getUri();
                if (isStatic(uri)) {
                    httpResponse.writeFile(Service.class.getResource(uri).getFile());
                } else {
                    httpResponse.writeFile(Service.class.getResource("/404.html").getFile());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                accept.close();
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static boolean isStatic(String uri) {
        if (uri == null) {
            return false;
        }
        String[] suffixs = {"jpg", "html"};
        for (String suffix : suffixs) {
            if (uri.endsWith(suffix)) {
                return true;
            }
        }
        return false;
    }
}
public class HttpRequest {
    private String method;// 请求方法
    private String uri;//请求的URI地址  在HTTP请求的第一行的请求方法后面
    private String host;//请求的主机信息

    public HttpRequest(InputStream inputStream) {
        this.parse(inputStream);
    }

    private void parse(InputStream is) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
        String line = null;
        try {
            // 要注意:sock 的输入流和普通的文件流不同,它不知道客户端是否还在输出信息
            // 如果不主动放弃读,继续往下读的话,就会阻塞
            while ((line = br.readLine()) != null) {
                // 因为http协议有报文头,空行 作为结束行
                if(StringUtils.isBlank(line)){
                    break;
                }
                doParse(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } /*finally {
            try {
                is.close();  // 不能关闭,关闭流会导致当前链接被关闭,外部不能继续操作 java.net.SocketException: Socket is closed
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
    }

    /**
     * 开始具体解析每一行的报头内容
     * @param line
     */
    private void doParse(String line) {
        // 简单提取
        if (line.startsWith("GET")) {
            this.setMethod("GET");
            this.setUri(line.substring(4, line.lastIndexOf("HTTP") -1));
        } else if (line.startsWith("Host")) {
            this.setHost(line.substring(6, line.length()));
        }
    }

    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;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }
}
public class HttpResponse {
    private OutputStream os = null;

    public HttpResponse(OutputStream os) {
        this.os = os;
    }

    /**
     * 响应静态文件
     * @param path
     */
    public void writeFile(String path) throws IOException {
        sendResponseHeaders(path);
        FileInputStream is = new FileInputStream(path);
        int len = 0;
        byte[] buf = new byte[1024];
        while ((len = is.read(buf)) != -1) {
            os.write(buf, 0, len);
        }
        os.flush();
        is.close();
        os.close();
    }

    /**
     * 模拟返回响应头
     * @param path
     */
    private void sendResponseHeaders(String path) {
        File file = new File(path);
        PrintStream writer = new PrintStream(os);

        writer.println("HTTP/1.0 200 OK");// 返回应答消息,并结束应答
        writer.println("Content-Length:" + file.length());// 返回内容字节数
        if (path.endsWith(".html")) {
            writer.println("Content-Type:text/html;charset=UTF-8");
        }

        writer.println();// 根据 HTTP 协议, 空行将结束头信息
    }
}

在resources中增加几个html和jpg图片,启动后可访问测试。

2.1.5.2.1. 总结

整个过程其实就是在对http协议的请求头和响应头做解析质押,只要对http协议足够了解,再结合自己的需求一般能写出一个小东西来玩玩的。

© All Rights Reserved            updated 2017-02-14 23:23:34

results matching ""

    No results matching ""