- 最近笔者读了《深入剖析tomcat》这本书(原作:《how tomcat works》),
- 发现该书简单易读,每个章节循序渐进的讲解了tomcat的原理,
- 本文为记录第一章的知识点以及源码实现(造轮子)。
HTTP 协议就是咱们 web 服务器与浏览器交互的协议,具体的知识点以及背景本文就不再累述。那么举一个简单的例子就好:
- GET /index.html HTTP/1.1
- Host: www.baidu.com
- ...
- HTTP/1.1 200 OK
- ...
- <html>
- <head>
- <title>百度一下你就知道</title>
- </head>
- <body>
- ....
- </body>
- </html>
那么其实通过上面的例子我们可以发现,静态 (这里指的是 html / 图片 / CSS 等)web 服务器的实现也是比较简单的:
- 用户请求-->服务器寻找相应的资源-->服务器输出对应的资源-->浏览器展示给用户
- 在这里使用java socket api实现简单的静态web服务器。
- //开启socket server 8080端口监听.
- ServerSocket server = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
- try (Socket accept = serverSocket.accept();
- InputStream inputStream = accept.getInputStream();
- OutputStream outputStream = accept.getOutputStream()) {
- //解析用户的请求
- Request request = new Request();
- request.setRequestStream(inputStream);
- request.parseRequest();
- //生成响应对象并响应静态资源
- Response resp = new Response(outputStream, request);
- resp.accessStaticResources();
- } catch (IOException e) {
- LOGGER.warn("catch from user request.",e);
- }
- //关闭服务器
- serverSocket.close();
- 主要功能:将用户请求(socket的inputStream流)解析为字符串,提取请求中的URI
解析字符串代码如下:
- StringBuilder requestStr = new StringBuilder();
- int i;
- //new 一个 byte缓冲数组
- byte[] buffer = ArrayUtil.generatorCache();
- try {
- i = inputStream.read(buffer);
- } catch(IOException e) {
- e.printStackTrace();
- i = -1;
- }
- //将读取到的byte转为String
- for (int j = 0; j < i; j++) {
- requestStr.append((char) buffer[j]);
- }
- //解析请求的字符串,提取请求的URI
- this.parseURI(requestStr.toString());
那么请求的信息被我们解析成字符串了,我们怎么知道他想请求什么静态资源呢?
那我们把解析字符串打印一下:
- System.out.println(requestStr.toString());
GET /index.html HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Cache-Control: max-age=0 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36 Upgrade-Insecure-Requests: 1 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
可以很明显的看到,加粗的地方就是我们要提取的 URI,那么怎么提取呢?细心的我们发现了,/index.html 这段字符串前后都有一个空格!行,那我们可以直接用 String 的 indexOf 方法解析,参考代码如下:
- // 获取/index.html 前面的那个空格索引
- int oneSpace = requestStr.indexOf(" ");
- //获取/index.html 后面的那个空格索引
- int twoSpace = requestStr.indexOf(" ", oneSpace + 1);
- //截取获得用户请求URI
- uri = requestStr.substring(oneSpace + 1, twoSpace);
- 上面Request对象已经把用户想请求的资源解析出来了,那么Response的功能就是找到这个文件,
- 使用Socket的outputStream把文件作为字节流输出给浏览器,就可以将我们的HTML显示给用户啦~
那么这个项目我们的静态文件放在那里呢?来看看我们的项目结构:
- - main - java java代码 - resources - webroot 存放我们静态资源的文件夹
因为是只使用 MAVEN 构建项目,我们也没使用 Spring 等框架,如何定位到 webroot 这个文件夹呢?参考了网上的代码:
- String WEB_PROJECT_ROOT = HttpServer.class.getClassLoader().getResource("webroot").getFile().substring(1);
前面的疑惑都解决了,接下来我们就直接把对应的文件找到给写回去就完事了~ 伪码如下:
- //根据请求URI找到用户对应请求的资源文件
- File staticResource = new File(HttpServer.WEB_PROJECT_ROOT + request.getUri());
- //资源存在
- if (staticResource.exists() && staticResource.isFile()) {
- outputStream.write(this.responseToByte(200, "OK"));
- write(staticResource);
- //资源不存在,使用默认的404返回
- } else {
- staticResource = new File(HttpServer.WEB_PROJECT_ROOT + "/404.html");
- outputStream.write(this.responseToByte(404, "file not found"));
- write(staticResource);
- }
其中,responseToByte() 这个方法只负责将响应行输出:
- HTTP / 1.1 200 OK
资源不存在时咱们就输出:
- HTTP / 1.1 404 file not found
write() 方法也很简单,将传入的 file 对象转成流并使用 socket 的 outputStream 输出
- try (FileInputStream fis = new FileInputStream(file)) {
- byte[] cache = new byte[1024];
- int read;
- while ((read = fis.read(cache, 0, 1024)) != -1) {
- outputStream.write(cache, 0, read);
- }
- }
运行 main 方法,打开我们的浏览器输出 127.0.0.1/index.html 按下回车,可以看到结果如图:
试试随便输入一个不存在的资源:
按下 F12 看看 Http 请求和响应分别是怎样的:
- 请求:GET / abc.html HTTP / 1.1Host: 127.0.0.1 : 8080其他请求头忽略...响应:HTTP / 1.1 404 file not found < !DOCTYPE html > <html lang = "en" > <head > < meta charset = "UTF-8" > < title > 404 not found ! </title></head > <body > < h1 > 请求页面不存在! < /h1></body > </html>/
到这里,咱们的 Tomcat 1.0 web 服务器就已经开发完成啦(滑稽脸),已经可以实现简单的 html 和 css、图片等资源的访问等功能,下一章咱们来实现以下简单的 Servlet 容器功能开发(ZHONG TOU XI)敬请期待~
PS:本章源码已上传 github SimpleTomcat
来源: https://juejin.im/post/5a473a406fb9a0451f3146f6