今天 Hack 了 WEBrick Thin Unicorn 跟 Puma.
Hack 的主要目的是:想知道关于 large request body 各个 server 是怎么处理的。以及我需要在程序中获取 request 的 body.
Webrick 会将 body 一股脑的塞到 @env["rack.input"] 里面去。所以等于是没做任何处理。当然作为原生自带 server. 这种假设可以说 能够接受。缺点就是别人塞超大对象。内存会爆。当然小对象可以直接获取到整个 body.
下面说说一些高端的 Server.
高端 Server 对于这方面的处理基本都遵循了一个原则,就是用 tempfile, 如果 body 大过某一个值,就会将对象写入一个临时文件里面去。如果不大于,那就直接写入 @env["rack.input"] .
Thin 里有一个的 MAX_BODY 常量 是:
MAX_BODY = 1024 * (80 + 32)
也就是说有 112k.
当 body 大于 112k , 就会生成一个 临时文件继续接收 body.
Thin 的 MAX_BODY 没有可配置的选项,是固定的。
Unicorn 也有一个 MAX_BODY 常量,Unicorn 的 MAX_BODY 是这样的:
MAX_BODY = 1024 * 112
当 body 大于 112k 的时候 也会生成一个临时文件继续接受。
并且有特殊的对象去处理这个 IO. Unicorn::TempIO
并且实现了 read 等方法。
并且 Unicorn 的 MAX_BODY 是可以配置的。
Puma 内部也有一个 MAX_BODY 的常量。
是这样定义的:
MAX_HEADER = 1024 * (80 + 32)
MAX_BODY = MAX_HEADER
Puma 也用了一个特殊的对象来处理这个 IO . 叫做 Puma::NullIO
.
NullIO 把自己伪装成一个 File, 但是 read 方法只会返回 nil 或者 "". 也就是说你在 NullIO 里是拿不到 body 的。
其实是能说明一点,当我想在 程序里通过 @env['rack.input'] 或者 @request.body 获取 body 的时候:
当 body 小于 112k 的时候:
WEBrick Thin Unicorn Puma 都可以完美工作。
当 body 大于 112k 的时候:
就只有 WEBrick 跟 Unicorn 可以工作。
虽然大家都有 read 方法可以用,但是有的能读到,有的读不到的赶脚真的很差!!!
这里有一些 hack 过程的记录,有些文件的位置:https://gist.github.com/3433666
我咋觉得*sgi.input
默认就是直接把 HTTP 连接的 socket file 直接映射过去,啥也别做,至于后面咋处理是应用的事情,*SGI Server
不该干这件事,因为只有应用才知道这里应该是啥。
而且 CGI 就是直接把输入映射到标准输入的。
其实爆内存有难度的,HTTP Server 默认配置的上传大小是有限制的,实在不行自己设小一点嘛。当然,你不判断就把整个输入先读到内存里就是你的错了...
#3 楼 @Saito 反正我那个 Server 就是直接 env['wsgi.input'] = socket.makefile('r')
的,跑了几个礼拜也没碰到过问题。其实只有在应用层你才知道这里到底该出现啥。
应用层处理应该更好一些,对于不需要处理的请求,就不浪费时间在把它写到临时文件上面了。
Server 不知道到底是参数还是 stream。而且,文件上传会是个 multipart,你还是需要从里面提取 POST 参数的。在 Server 这层做判断,按你的说法,multipart 既可能是 stream 也可能是 string,等于没判断。再考虑到可以有一些恶意的客户端的存在,你不能根据 header 来区别 body 的内容。
http://unicorn.bogomips.org/KNOWN_ISSUES.html
On Ruby 1.8 prior to Ruby 1.8.7-p248, *BSD platforms have a broken stdio that causes failure for file uploads larger than 112K. Upgrade your version of Ruby or continue using Unicorn 1.x/3.4.x.
#13 楼 @quakewang Puma 的 NullIO 的 read 是这样实现的:
# Mimics IO#read with no data
#
def read(count=nil,buffer=nil)
(count && count > 0) ? nil : ""
end
Thin 用的是 StringIO. 但是它没往里面写入。
只有 unicorn 实现了真正的 read.
def read(*args)
@socket ? tee(super) : @tmp.read(*args)
end
#15 楼 @Saito Thin 我记得是超过上限就进入临时文件: https://github.com/macournoyer/thin/blob/master/lib/thin/request.rb#L150
看上去应该是支持 read 方法的亚,现在没有开发环境,明天我测试看看
#17 楼 @quakewang https://github.com/macournoyer/thin/issues/126 给 Thin 提了一个 issue. 等待有人回复。
#22 楼 @ranmocy 如果没记错的话,在 Thin 上是不行的。用 unicorn 部署是没问题的。
我的 grack 主要是在 gitlabhq 里面实现 valid? 方法的时候用到了,主要是为了实现 gitlabhq 的权限控制。
如果没有分支权限问题的话,是不需要获取头的。https://github.com/gitlabhq/gitlabhq/blob/master/lib/gitlab/backend/grack_auth.rb#L76-L85
valid? 的时候先读了一次是因为要 解析 body 体,获取 git 的 push 信息.. 从而判断权限。
server.rb 内部的就不需要做这个 rewind 了。( 读过一次之后,头指针变化之后,重置头指针..
具体是什么还真有点忘记了。貌似是我最后解决了所有 Server 的 Grack 问题... 现在的版本都是没问题的。orz..