предисловие
最近笔者读了《深入剖析tomcat》这本书(原作:《how tomcat works》),发现该书简单易读,每个
章节循序渐进的讲解了tomcat的原理,在接下来的章节中,tomcat都是基于上一章新增功能并完善,
到最后形成一个简易版tomcat的完成品。所以有兴趣的同学请按顺序阅读,本文为记录第一章的知识点
以及源码实现(造轮子)。
Как добиться
Протокол HTTP – это протокол, по которому наш веб-сервер взаимодействует с браузером. В этой статье не будут повторяться его конкретные знания и предыстория. Итак, возьмем простой пример:
- Введите http://www.baidu.com в браузере и нажмите Enter.
- Браузер, вероятно, отправил следующий http-запрос на сервер Baidu:
GET /index.html HTTP/1.1
Host: www.baidu.com
...
- Когда веб-сервер Baidu получает наш запрос, он находит соответствующий ресурс сервера и отвечает соответствующим образом:
HTTP/1.1 200 OK
...
<html>
<head>
<title>百度一下你就知道</title>
</head>
<body>
....
</body>
</html>
Таким образом, в приведенном выше примере мы можем обнаружить, что реализация статического (здесь имеется в виду html/image/css и т. д.) веб-сервера относительно проста:
Код
在这里使用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Перед и после этой строки есть пробел! Хорошо, тогда мы можем разобрать его напрямую с помощью метода indexOf String.Ссылочный код выглядит следующим образом:
// 获取/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 и другие фреймворки, как найти корневую папку? Обратитесь к коду в Интернете:
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() также очень прост: он преобразует входящий файловый объект в поток и использует 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);
}
}
увидеть эффект
Запускаем основной метод, открываем наш браузер и выводим 127.0.0.1/index.html и нажимаем Enter, вы можете увидеть результат как показано на рисунке:
Попробуйте наугад ввести несуществующий ресурс:
Нажмите F12, чтобы увидеть, как выглядят Http-запрос и ответ:
请求:
GET /abc.html HTTP/1.1
Host: 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 был разработан (забавное лицо), и он может реализовывать простые функции доступа к ресурсам html, css, изображения и др. В следующей главе мы реализуем следующую простую разработку функций контейнера сервлетов:
Реализуйте Tomcat вместе со мной (2): реализация простого контейнера сервлетов
PS: Исходный код этой главы выложен на github. SimpleTomcat