Rack Spec
This specification aims to formalize the Rack protocol. You can (and should) use Rack::Lint to enforce it. When you develop middleware, be sure to add a Lint before and after to catch all mistakes.
本说明的目的是为了规范化Rack协议。可以(且必须)使用Rack::Lint检查并确保。在开发中间件时,前后添加Lint可以捕获所有的错误。
Rack applications
A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.
Rack应用程序是可以响应call调用的Ruby对象(不是类)。其中,call函数接受一个环境参数,并返回包含三个值的数组:The status, the headers, and the body
The Environment
The environment must be an instance of Hash that includes CGI-like headers. The application is free to modify the environment. The environment is required to include these variables (adopted from PEP333), except when they’d be empty, but see below.
环境变量必须是Hash的实例对象,其中包含着CGI类似的头信息。应用程序可以自由的修改环境变量。环境变量必须包含如下的变量:
REQUEST_METHOD | HTTP请求方法,比如`GET`或者`POST`。该变量不能为空,并且总是必须的 |
SCRIPT_NAME | 请求URL初始分割的路径部分是应用程序的对象,所以应用程序知道其虚拟位置。如果应用程序对应为服务器的根目录,该变量可以为空。 |
PATH_INFO | 请求URL路由的剩余部分,标明应用程序中请求目标的虚拟地址。这也能是空的字符串。如果请求URL的目标是应用程序的根路径。从URL中解析时,该值可能是%编码 |
QUERY_STRING | 请求URL中?之后的部分,即使为空,也必须存在 |
SERVER_NAME, SERVER_PORT | 组合`SCRIPT_NAME`和`PATH_INFO`,变量可以用作完整的URL。但是注意,如果`HTTP_HOST`存在的话,会优先使用`HTTP_HOST`而不是`SERVER_PORT`来重建请求URL。`SERVER_NAME`和`SERVER_PORT`都不能为空,并且都是必须的. |
HTTP_ Variables | 变量对应于客户端支持的HTTP请求头(例如,那些以`HTTP_`开头的变量). 这些变量和HTTP请求头部一一对应。更多特定的行为参考<a href=“tools.ietf.org/html/rfc3875#section-4.1.18”> RFC3875 section 4.1.18</a>. |
除了上面的那些变量,Rack环境变量页支持如下的这些Rack特定的变量:
rack.version | The Array representing this version of Rack. See Rack::VERSION, that corresponds to the version of this SPEC. |
rack.url_scheme | http或者https, 取决于请求的URL。疑问:http和https不是不同的协议吗,怎么编程URL模式了 |
rack.input | See below, the input stream. |
rack.errors | See below, the error stream. |
rack.multithread | 如果应用程序对象可能被相同进程中的不同线程同时调用,则设置为true; 否则,设置为false。 |
rack.multiprocess | 如果应用程序对象可以被其他的进程同时调用,则设置为true,否则设置为false。 |
rack.run_once | 如果服务器希望(但不保证)应用程序在其进程的生命周期中,仅会被调用一次,则设置为true。通常而言,这仅在基于CGI(或类似的东西,FCGI)服务器时,设置为ture |
rack.hijack? | 如果服务器支持连接劫持,则存在并设置为true。 |
rack.hijack | an object responding to #call that must be called at least once before using `rack.hijack_io`. It is recommended #call return rack.hijack_io as well as setting it in env if necessary. |
rack.hijack_io | if rack.hijack? is true, and rack.hijack has received #call, this will contain an object resembling an IO. See hijacking. |
Additional environment specifications have approved to standardized middleware APIs. None of these are required to be implemented by the server.
附加的环境变量主要用来提供标准的中间件API,这些对服务器的实现而言,都不是必须的。
rack.session | 用来存储请求的session数据的类Hash接口,该存储接口必须实现: store(key, value) (别名为 []=); fetch(key, default = nil) (别名为[]); delete(key); clear; |
rack.logger |
用来记录消息的通用对象接口(即在诸多语言和框架中均有实现),其对象必须实现如下的函数: |
The server or the application can store their own data in the environment, too. The keys must contain at least one dot, and should be prefixed uniquely. The prefix rack. is reserved for use with the Rack core distribution and other accepted specifications and must not be used otherwise. The environment must not contain the keys HTTP_CONTENT_TYPE
or HTTP_CONTENT_LENGTH
(use the versions without HTTP_). The CGI keys (named without a period) must have String values. There are the following restrictions:
服务器或应用程序也可在环境变量中存储数据。但是,键名必须要包含至少一个点,而且前缀必须是独一无二的。rack前缀被保留用作Rack核心以及其他已接受的规范,不可作其他用处。环境变量中也不能使用HTTP_CONTENT_TYPE
或者HTTP_CONTENT_LENGTH
的key,如果一定要用,请使用不带HTTP_前缀的key。不带有时期的CGI键必须要有字符值。也存在如下的这些约束:
rack.version
必须是一个整数数组rack.url_scheme
必须是http或httpsrack.input
必须是有效的输入流rack.errors
必须是有效的错误流rack.hijack_io
必须是有效的hijack流- REQUEST_METHOD必须是有效的token
- SCRIPT_NAME如果不为空,必须以 / 开头
- PATH_INFO如果不为空,必须以/开头
- CONTENT_LENGTH必须由数字组成
SCRIPT_NAME
或者PATH_INFO
其中之一必须设置。 如果SCRIPT_NAME
为空,PATH_INFO
必须是/。如果SCRIPT_NAME
是空的,SCRIPT_NAME
不能为/,但是可以为空
输入流
The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
输入流是类似IO的对象,其中包含了原始HTTP POST的数据。恰当的时候,其外部的编码必须是ASCII-8BIT,并且必须是二进制模式(Ruby 1.9的兼容模式中)。输入流对象必须响应gets,each,read以及rewind方法。
- gets must be called without arguments and return a string, or nil on EOF.
- gets方法必须以不带参的方式调用,并返回为字符串,或者是nil on EOF
- read behaves like IO#read. Its signature is read([length, [buffer]]). If given, length must be a non-negative Integer (>= 0) or nil, and buffer must be a String and may not be nil. If length is given and not nil, then this method reads at most length bytes from the input stream. If length is not given or nil, then this method reads all data until EOF. When EOF is reached, this method returns nil if length is given and not nil, or “” if length is not given or is nil. If buffer is given, then the read data will be placed into buffer instead of a newly created String object.
- read方法和IO#read类似,其方法申明为read([length, [buffer]])。如果给定,length必须为非负数(>= 0)或nil,buffer必须为一个字符串或可能为nil。如果给定长度,且不为nil,read方法最多重输入流中读取length所指定的字节。如果没有给定长度或为nil,read方法将读取EOF之前的数据。如果到达了EOF,在给定length且长度不为空时返回为nil,在length未给定且为nil时返回为”“。如果给定buffer,读取的数据将放置到buffer中,而不是新建一个新的字符串对象
- each must be called without arguments and only yield Strings.
- each方法不带任何参数,且仅仅yield字符串
- rewind must be called without arguments. It rewinds the input stream back to the beginning. It must not raise Errno::ESPIPE: that is, it may not be a pipe or a socket. Therefore, handler developers must buffer the input data into some rewindable object if the underlying input stream is not rewindable.
- rewind方法不带任何参数。将回退到输入流的开始。不能抛出Errno::ESPIPE异常,即不能是管道或者套接字。所以,处理器开发者必须处理如果潜在的输入流不可回退,必须以某种方式缓存数据数据。
- close must never be called on the input stream.
- 不能在输入流上调用close方法
输出流(The Error Stream)
The error stream must respond to puts, write and flush.
错误流必须实现puts,write以及flush
- puts must be called with a single argument that responds to to_s.
- puts的唯一参数必须可以响应to_s方法
- write must be called with a single argument that is a String.
- write的唯一参数必须是字符串
- flush must be called without arguments and must be called in order to make the error appear for sure.
- flush不带任何参数,用来按顺序显示错误
- close must never be called on the error stream.
- close页不能在错误流上调用
劫持(Hijacking)
Request (before status)
If rack.hijack? is true then rack.hijack must respond to #call. rack.hijack must return the io that will also be assigned (or is already present, in rack.hijack_io.
如果rack.hijack?为真,则rack.hijack必须响应#call方法。rack.hijack必须返回可被赋值的io对象()
rack.hijack_io
must respond to: read, write, read_nonblock
, write_nonblock
, flush, close, close_read
, close_write, closed?
rack.hijack_io
对象必须实现read, write, read_nonblock
, write_nonblock
, flush, close, close_read
, close_write, closed?这些方法。
The semantics of these IO methods must be a best effort match to those of a normal ruby IO or Socket object, using standard arguments and raising standard exceptions. Servers are encouraged to simply pass on real IO objects, although it is recognized that this approach is not directly compatible with SPDY and HTTP 2.0.
这些IO方法的语义必须恰好匹配正常的Ruby IO对象或者Socket对象,使用标准参数并抛出标准异常。服务器可方便的传递真实的IO对象,尽管这个方法方法不直接兼容SPDY和HTTP 2.0。
IO provided in rack.hijack_io should preference the IO::WaitReadable and IO::WaitWritable APIs wherever supported.
rack.hijack_io中提供的IO无论何时,都必须支持IO::WaitReadable和IO::WaitWritable的API。
There is a deliberate lack of full specification around rack.hijack_io
, as semantics will change from server to server. Users are encouraged to utilize this API with a knowledge of their server choice, and servers may extend the functionality of hijack_io to provide additional features to users. The purpose of rack.hijack is for Rack to “get out of the way”, as such, Rack only provides the minimum of specification and support.
在规范中,rack.hijack_io
故意缺失的是语义是从服务器到服务器的。用户鼓励使用带有服务器知识API,服务器可以扩展hijack_io的功能,从而为用户提供附加的特性。Rack中rack.hijack的目标是”滚出去”,所以Rack只提供最小化的规范支持。
If rack.hijack? is false, then rack.hijack should not be set. If rack.hijack? is false, then rack.hijack_io should not be set.
如果rack.hijack?为false,rack.hijack不应该被设置,rack.hijack_io也不被设置。
Response (after headers)
It is also possible to hijack a response after the status and headers have been sent. In order to do this, an application may set the special header rack.hijack to an object that responds to call accepting an argument that conforms to the rack.hijack_io protocol.
对hijack而言,在状态和头部信息已经被发送之后,依然可以劫持响应。为了做到这个,应用程序可能为对象设置特别的rack.hijack头部,其中对象将会响应接受一个参数的call调用,从而符合rack.hijack_io协议。
After the headers have been sent, and this hijack callback has been called, the application is now responsible for the remaining lifecycle of the IO. The application is also responsible for maintaining HTTP semantics. Of specific note, in almost all cases in the current SPEC, applications will have wanted to specify the header Connection:close in HTTP/1.1, and not Connection:keep-alive, as there is no protocol for returning hijacked sockets to the web server. For that purpose, use the body streaming API instead (progressively yielding strings via each).
在头部已经发送之后,hijack调用已经调用后,应用程序需要对IO对象剩余的生命周期进行负责。应用程序负责维护HTTP语义。特别要注意的是:本规范的大多数情况中,应用程序需要在HTTP/1.1的头部中指定Connection:close,而不是Connection:keep-alive,这是因为没有针对web服务器返回劫持的socket的协议。处于这个目的,使用body流API。
Servers must ignore the body part of the response tuple when the rack.hijack response API is in use. 当使用rack.hijack响应API时,服务器忽略响应体的body部分。
The special response header rack.hijack must only be set if the request env has rack.hijack? true.
如果请求env中rack.hijack?设置为true时,才会设置特殊的rack.hijack头部。
约定(Conventions)
- Middleware should not use hijack unless it is handling the whole response.
- 除非中间件处理整个响应过程,中间件不应该使用hijack
- Middleware may wrap the IO object for the response pattern.
- 中间件为响应模式包裹IO对象
- Middleware should not wrap the IO object for the request pattern. The request pattern is intended to provide the hijacker with “raw tcp”.
- 中间件不因该为请求模式包装IO对象,请求模式尝试以原始tcp的提供hijacker
The Response
响应必须包含状态,请求头,内容类型,内容长度以及响应体,这些是必须包含的,Rack应用中。
The Status
This is an HTTP status. When parsed as integer (to_i), it must be greater than or equal to 100.
The Headers
The header must respond to each, and yield values of key and value. Special headers starting “rack.” are for communicating with the server, and must not be sent back to the client. The header keys must be Strings. The header must not contain a Status key, contain keys with : or newlines in their name, contain keys names that end in - or _, but only contain keys that consist of letters, digits, _ or - and start with a letter. The values of the header must be Strings, consisting of lines (for multiple header values, e.g. multiple Set-Cookie values) separated by “\n”. The lines must not contain characters below 037.
The Content-Type
There must not be a Content-Type, when the Status is 1xx, 204, 205 or 304.
The Content-Length
There must not be a Content-Length header when the Status is 1xx, 204, 205 or 304.
The Body
The Body must respond to each and must only yield String values. The Body itself should not be an instance of String, as this will break in Ruby 1.9. If the Body responds to close, it will be called after iteration. If the body is replaced by a middleware after action, the original body must be closed first, if it responds to close. If the Body responds to to_path, it must return a String identifying the location of a file whose contents are identical to that produced by calling each; this may be used by the server as an alternative, possibly more efficient way to transport the response. The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
响应体(Body)必须可以调用each方法,并且只能产生(yield)字符串值。在Ruby 1.9中,由于不支持String.each,Body自身不能是字符串的实例。如果Body具有close方法,则在调用each之后调用close方法(实现一些清除工作)。如果body在执行动作之后,由中间件代替,原先的body必须先close掉(若其可调用close方法)。如果body响应to_path方法,则to_path方法必须返回一个表示文件位置的字符串,文件中的内容调用each方法生成的。to_path方法对服务器而言是可选的,并且是传输响应的有效方法。响应体通常是字符串数组,应用程序自身实例或类似File的对象(所谓Duck类型)。
一些好处:
- 只要能调用each方法,可以是任何对象,each只产生字符串
- 减少不必要的资源耗损和不必要的工作,比如巨大的文件对象
- 更多可选择和优化的机会
Thanks
Some parts of this specification are adopted from PEP333: Python Web Server Gateway Interface v1.0 (www.python.org/dev/peps/pep-0333/). I’d like to thank everyone involved in that effort.
该规范的改编自PEP333: Python web服务器网关接口.
后记
看完之后,果然没有实在感,也是知道了Rack中间件,call方法调用,环境变量,响应之类的。