使用 Ruby 自制 Web 服务器
文章目录
少年,你是否曾用 Ruby 写过专属于你自己的 web 服务器?虽然我们已经有了如下服务器应用:
-
Puma
-
Thin
-
Unicorn
但是我还是觉得自己写一个简单的 web 服务器会是学习服务器运行原理的绝佳机会。今天你算是赶上了。 让我们一步一步来!
第一步:监听连接
首要的第一件事是监听 80 端口的 TCP 新连接。 我之前写过一篇 Ruby中的网络编程,所以这里我就不多解释原理了,直接上代码:
|
|
运行上述代码,我们得到了一个在 80 端口上接受请求的 server。目前来说它做的工作还不多,不过也足够你看到进来的请求是什么样子的了。
“在 Linux/Mac 系统中使用 80 端口可能需要 root 权限,作为替代,你也可以换成其他大于 1024 的端口,我比较钟意 8080”
使用浏览器或者是 curl
可以简单地构造一个请求。
如下是你这么做了之后所能看到的打印信息:
|
|
这是一个 HTTP 请求。HTTP 是一个为了在 web 浏览器和 web 服务器之间通信的文本协议。正式的协议细节详见这里:https//tools.ietf.org/html/rfc7230。
第二步:解析请求
现在我们需要将请求分割成服务器所能理解的小份形式。要实现这样的效果我们可以自己实现解析器或者复用已有的工具。为了加深对请求每一部分的理解,我们还是撸起袖子自己干吧。
这副配图应该有帮助
请求头里包括浏览器缓存、虚拟地址和数据压缩等等,不过对于一个基础的服务器实现来说,我们可以忽略这些要素。
认识到如下事实对我们实现自己的解析器大有俾益:请求数据是被换行符号 \r\n
所分割的。为保持简单,我们不会做错误处理或数据校验。
那么我们就得到了如下代码:
|
|
以上将会返回经过解析的请求数据。既然我们已经得到了规范化后的请求数据,现在可以为客户端构造响应了。
第三步 准备好发送响应
在返回响应之前我们需要检查请求资源是否可用。换句话说,我们需要检查文件是否存在。
以下是为此准备的代码:
|
|
代码中包含两种情况:
- 首先,如何请求路径是
/
的话,我们假设所希望请求的文件是index.html
。 - 其次,如果请求文件存在,我们便将文件内容连同一个 OK 响应发回。
不过如果文件不存在的话,我们就要返回经典的 404 Not Found
响应了。
常用的 HTTP 响应码
供参考。
Code | 描述 |
---|---|
200 | OK |
301 | Moved permanently |
302 | Found |
304 | Not Modified |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not found |
500 | Internal Server Error |
502 | Bad Gateway |
响应类和方法
以下是我们上面用到的 “send” 方法:
|
|
以下是 Response
类:
|
|
响应由模板和字符串插值构成。到此阶段之后只要将所有这些代码组合进之前的接受连接的
loop
里,我们就得到了一个功能完备的服务器了。
|
|
试着在SERVER_ROOT
目录下添加些 HTML
文件,然后你应该就能从浏览器加载它们了。其他一些包括图像等静态资源文件也同样如此。
当然了,真实世界中的 web 服务器包含许多我们未在这里实现的功能。
我在这里列出了一些缺失的功能,作为提高练习留给你们(熟能生巧!!!):
- 虚拟主机
- 多媒体类型
- 数据压缩
- 访问控制
- 多线程
- 请求验证
- 解析查询字符串
- POST 请求体解析
- 浏览器缓存(响应 304 状态码)
- 重定向
安全相关
接受并操作用户的输入永远是一件有风险的事情。在我们的小服务器项目中,用户输入是HTTP请求。
我们引入了一个被称为“路径遍历”的漏洞。用户可以访问任何服务器应用用户有权限访问的路径,包括那些不在我们的 SERVER_ROOT
路径下的目录。
下面这行代码是导致此问题的关键:
|
|
你可以亲自实验一下,看看这问题是怎么产生的。为此你需要“手动”构造 HTTP
请求,因为大多数 HTTP客户端(包括 curl
)会预先处理请求 URL 并移除会导致此漏洞的部分。
你可以用 netcat 尝试一下。
以下是其中一种生成办法:
|
|
如果你是类 Unix 系统的话,上述请求会返回 /etc/passwd
文件的内容。其中的原理是因为双点号(..
)表示上层目录,于是你便“跳出“了 SERVER_ROOT
目录。
某一个解决办法是将多个点号”压缩成一个:
|
|
解决安全问题要时刻有“以己之矛,攻己之盾”的准备。例如,如果你只是简单的
path.gsub!("..", ".")
,那么使用三重点号(…)便可以轻易绕过此限制。
完整可运行的代码
我知道本文中的代码四散在文章各处,所以如果你想得到完整,可运行的代码的话。。。
链接在此:
https://gist.github.com/matugm/efe0a1c4fc53310f7ac93dcd1f041f6c#file-web-server-rb
好好享用吧!
总结
在本文中,你学到了如何监听新连接、HTTP 请求是什么样子了和如何解析它们。同样地,你学会了如何用响应码和请求文件的内容(如何提供的话)构造响应。
最后,你学到了什么是“路径遍历”漏洞和如何避免它。
希望本文能对你有所帮助!不要忘了订阅我的博客(地址在下面),这样你就不会漏掉我的每一篇文章啦:)