0%

tornado.web 消息响应实现

引言

这部分笔记主要是简单分析 Tornado 中 Http 响应相关的代码,但没有涉及到安全相关的 cookie 实现以及 HTML 模板引擎。

HTTPConnection

tornado.httputil 模块定义了 Tornado 的响应接口 HTTPConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class HTTPConnection(object):
"""Applications use this interface to write their responses.

.. versionadded:: 4.0
"""
def write_headers(self, start_line, headers, chunk=None, callback=None):
"""Write an HTTP header block.

:arg start_line: a `.RequestStartLine` or `.ResponseStartLine`.
:arg headers: a `.HTTPHeaders` instance.
:arg chunk: the first (optional) chunk of data. This is an optimization
so that small responses can be written in the same call as their
headers.
:arg callback: a callback to be run when the write is complete.

Returns a `.Future` if no callback is given.
"""
raise NotImplementedError()

def write(self, chunk, callback=None):
"""Writes a chunk of body data.

The callback will be run when the write is complete. If no callback
is given, returns a Future.
"""
raise NotImplementedError()

def finish(self):
"""Indicates that the last body data has been written.
"""
raise NotImplementedError()

之前已经分析过的 tornado.http1connection.HTTP1Connection 是该接口的实现(另一个实现是 tornado.wsgi._WSGIConnection)。上述定义的三个接口方法注释很完整,简单来说:

  1. write_headers 方法用于写 Http 消息头,一次响应应该只调用一次。其中可选参数 chunk,是消息体数据,在响应数据较少的情况下灰常有用,很多时候我们都是调用一次该方法完成写消息头和消息体,而不会去单独调用 write 方法单独写消息体。

  2. write 方法用于写消息体。

  3. finish 方法用于告诉接口此次请求响应结束。

HTTP1Connection

write_headers/write

write 方法的实现基本上就是 write_headers 的一部分,这里就把二者的代码放在一起分析。相关代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def write_headers(self, start_line, headers, chunk=None, callback=None):
"""Implements `.HTTPConnection.write_headers`."""
if self.is_client:
self._request_start_line = start_line
# Client requests with a non-empty body must have either a
# Content-Length or a Transfer-Encoding.
# 不检查是否 Http/1.0 是不完备的。
self._chunking_output = (
start_line.method in ('POST', 'PUT', 'PATCH') and
'Content-Length' not in headers and
'Transfer-Encoding' not in headers)
else:
self._response_start_line = start_line
# 对于 HTTP/1.0 ``self._chunking_output=False``,不支持分块传输编码。
self._chunking_output = (
# TODO: should this use
# self._request_start_line.version or
# start_line.version?
self._request_start_line.version == 'HTTP/1.1' and
# 304 responses have no body (not even a zero-length body), and so
# should not have either Content-Length or Transfer-Encoding.
# headers.
start_line.code != 304 and
# No need to chunk the output if a Content-Length is specified.
'Content-Length' not in headers and
# Applications are discouraged from touching Transfer-Encoding,
# but if they do, leave it alone.
'Transfer-Encoding' not in headers)
# If a 1.0 client asked for keep-alive, add the header.
# HTTP/1.1 默认就是持久化连接,不需要单独指定。
# 假设客户端请求使用 HTTP/1.0 和 `Connection:Keep-Alive`,服务端响应时没有指定
# `Content-Length` (比如在 handler 中多次调用 flush 方法),那么响应数据就无法
# 判断边界,代码中应该对这个条件做特别处理。
if (self._request_start_line.version == 'HTTP/1.0' and
(self._request_headers.get('Connection', '').lower()
== 'keep-alive')):
headers['Connection'] = 'Keep-Alive'
if self._chunking_output:
headers['Transfer-Encoding'] = 'chunked'
if (not self.is_client and
(self._request_start_line.method == 'HEAD' or
start_line.code == 304)):
self._expected_content_remaining = 0
elif 'Content-Length' in headers:
self._expected_content_remaining = int(headers['Content-Length'])
else:
self._expected_content_remaining = None
lines = [utf8("%s %s %s" % start_line)]
# 通过 add 添加的响应头会输出多个,比如:“Set-Cookie” 响应头。
lines.extend([utf8(n) + b": " + utf8(v) for n, v in headers.get_all()])
for line in lines:
if b'\n' in line:
raise ValueError('Newline in header: ' + repr(line))
future = None
if self.stream.closed():
future = self._write_future = Future()
future.set_exception(iostream.StreamClosedError())
else:
# "写回调" 是一个实例字段 `_write_callback`,当上一次写操作还没有回调时就再次执行
# 写操作,那么上一次写操作的回调将被放弃(callback is not None)
if callback is not None:
self._write_callback = stack_context.wrap(callback)
else:
# 没有 callback 时,返回 Future(self._write_future)
future = self._write_future = Future()
# Headers
data = b"\r\n".join(lines) + b"\r\n\r\n"
# message-body
if chunk:
data += self._format_chunk(chunk)
self._pending_write = self.stream.write(data)
self._pending_write.add_done_callback(self._on_write_complete)
return future

def write(self, chunk, callback=None):
"""Implements `.HTTPConnection.write`.

For backwards compatibility is is allowed but deprecated to
skip `write_headers` and instead call `write()` with a
pre-encoded header block.
"""
future = None
if self.stream.closed():
future = self._write_future = Future()
self._write_future.set_exception(iostream.StreamClosedError())
else:
if callback is not None:
self._write_callback = stack_context.wrap(callback)
else:
future = self._write_future = Future()
self._pending_write = self.stream.write(self._format_chunk(chunk))
self._pending_write.add_done_callback(self._on_write_complete)
return future

def _on_write_complete(self, future):
if self._write_callback is not None:
callback = self._write_callback
self._write_callback = None
self.stream.io_loop.add_callback(callback)
if self._write_future is not None:
future = self._write_future
self._write_future = None
future.set_result(None)

def _format_chunk(self, chunk):
if self._expected_content_remaining is not None:
self._expected_content_remaining -= len(chunk)
if self._expected_content_remaining < 0:
# Close the stream now to stop further framing errors.
self.stream.close()
raise httputil.HTTPOutputError(
"Tried to write more data than Content-Length")
if self._chunking_output and chunk:
# Don't write out empty chunks because that means END-OF-STREAM
# with chunked encoding
#
# Each chunk: the number of octets of the data(hex number) + CRLF + chunk data + CRLF
return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n"
else:
return chunk

上述代码逻辑不复杂,但是有几点需要注意一下:

  1. self._chunking_output 是分块传输编码 Transfer-Encoding: chunked 启用标识,仅在 Http/1.1 提供。对于客户端请求而言,不检查是否 HTTP/1.0 是不完备的,会导致问题。

  2. Connection:Keep-Alive 响应头在 HTTP/1.1 是默认的,不需要特别设置,而 HTTP/1.0 则需要显示设置。所以服务端响应时要根据请求的 HTTP 版本及 Connection 值来决定是否发送 Connection:Keep-Alive。这里其实有一个问题:假设客户端请求使用 HTTP/1.0 和 Connection:Keep-Alive,服务端响应时没有指定 Content-Length (比如在 handler 中多次调用 flush 方法),那么响应数据就无法判断边界,代码中没有对这个条件做特别处理。两个解决方案:1. 检测到这种情况时响应非 Keep-Alive 模式;2. 只支持 HTTP/1.1 响应。

  3. write_headers 调用时没有指定 callback 时返回 Future 对象,否则返回 None,且在写操作完成后执行 callback

  4. 连续执行两次带 callback 的写操作,由于 callback 保存在实例字段 _write_callback中,当上一次写操作还没有回调时就再次执行下一次写操作,那么上一次的 callback 将被放弃。

注:octet 总是指 8bit,byte 通常情况下也是指 8bit,但是准确来说, byte 指的是计算机 CPU 可以独立寻址的最小内存单位。曾几何时,有些计算机的寻址并不是 8bit,不过现在的计算机几乎 octet 等价于 byte,仅当你要强调准确的 8bit 时就应该使用 octet 而不是 byte。

finish

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def finish(self):
"""Implements `.HTTPConnection.finish`."""
if (self._expected_content_remaining is not None and
self._expected_content_remaining != 0 and
not self.stream.closed()):
self.stream.close()
raise httputil.HTTPOutputError(
"Tried to write %d bytes less than Content-Length" %
self._expected_content_remaining)
if self._chunking_output:
if not self.stream.closed():
# `Transfer-Encoding:chunked`: The terminating chunk is a
# regular chunk, with the exception that its length is zero.
self._pending_write = self.stream.write(b"0\r\n\r\n")
self._pending_write.add_done_callback(self._on_write_complete)
self._write_finished = True
# If the app finished the request while we're still reading,
# divert any remaining data away from the delegate and
# close the connection when we're done sending our response.
# Closing the connection is the only way to avoid reading the
# whole input body.
if not self._read_finished:
self._disconnect_on_finish = True
# No more data is coming, so instruct TCP to send any remaining
# data immediately instead of waiting for a full packet or ack.
# 关闭 Nagle 算法,效果相当于让 socket 立即 flush 数据到客户端,随后将在
# `_finish_request` 中恢复 Nagle 算法。
self.stream.set_nodelay(True)
if self._pending_write is None:
self._finish_request(None)
else:
# 最后一次挂起的写操作完成后回调 `_finish_request` 方法。
self._pending_write.add_done_callback(self._finish_request)

def _finish_request(self, future):
# ``close`` 中还会执行一次,调整到后面执行更好
self._clear_callbacks()
# 服务端不需要支持长连接时,执行关闭操作
if not self.is_client and self._disconnect_on_finish:
self.close()
return
# Turn Nagle's algorithm back on, leaving the stream in its
# default state for the next request.
self.stream.set_nodelay(False)
if not self._finish_future.done():
self._finish_future.set_result(None)

def _clear_callbacks(self):
"""Clears the callback attributes.

This allows the request handler to be garbage collected more
quickly in CPython by breaking up reference cycles.
"""
self._write_callback = None
self._write_future = None
self._close_callback = None
if self.stream is not None:
self.stream.set_close_callback(None)

def close(self):
if self.stream is not None:
self.stream.close()
self._clear_callbacks()
if not self._finish_future.done():
self._finish_future.set_result(None)

finish 方法基本上就是做了这几件事:

  1. 检查指定长度的消息体是否发送完成或者在 Transfer-Encoding:chunked 模式下发送结束块。
  2. 如果在请求数据读取完成之前就响应完成就直接关闭连接。
  3. 设置响应结束标识 _write_finished,立即 flush 数据到客户端。
  4. 立即执行 _finish_request 方法或者等挂起的写操作完成后执行。

RequestHandler

languageRequestHandler 与响应直接相关的方法是 flushfinish,这两个方法内部都是委托 HTTP1Connection 来执行。

finish

finish 方法用来结束此次请求和响应。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def finish(self, chunk=None):
"""Finishes this response, ending the HTTP request."""
if self._finished:
raise RuntimeError("finish() called twice. May be caused "
"by using async operations without the "
"@asynchronous decorator.")

if chunk is not None:
self.write(chunk)

# Automatically support ETags and add the Content-Length header if
# we have not flushed any content yet.
if not self._headers_written:
if (self._status_code == 200 and
self.request.method in ("GET", "HEAD") and
"Etag" not in self._headers):
self.set_etag_header()
if self.check_etag_header():
self._write_buffer = []
self.set_status(304)
if self._status_code == 304:
assert not self._write_buffer, "Cannot send body with 304"
self._clear_headers_for_304()
elif "Content-Length" not in self._headers:
content_length = sum(len(part) for part in self._write_buffer)
self.set_header("Content-Length", content_length)

if hasattr(self.request, "connection"):
# Now that the request is finished, clear the callback we
# set on the HTTPConnection (which would otherwise prevent the
# garbage collection of the RequestHandler when there
# are keepalive connections)
#
# NOTE: 这里注释说请求处理完成时将 `HTTPConnection` 的连接关闭回调设置为 None,
# 否则长连接情况下当前 `RequestHandler` 实例由于被连接引用而不能被及时垃圾回收。
# 这个问题应该是 Tornado v4.0 之前由 `HTTPConnection` 处理 “连接保持”造成的,
# 之后的版本由于分离出 `HTTP1ServerConnection` 来处理连接保持已经不存在这个问题:
# 每次请求都是单独生成一个 `HTTP1Connection` 实例处理,并且在请求处理完成后会调用
# `_clear_callbacks` 方法自动清空回调(参见 `_read_message` 的 finally 块)。
self.request.connection.set_close_callback(None)

self.flush(include_footers=True)
# 按 `HttpServerRequest.finish` 的注释,该方法在 v4.0 已经放弃,而建议直接使用
# `request.connection.finish` 方法。
self.request.finish()
self._log()
self._finished = True
self.on_finish()
# Break up a reference cycle between this handler and the
# _ui_module closures to allow for faster GC on CPython.
self.ui = None

finish 时如果之前没有 flush 过数据则会自动为响应增加 “Etag” 和 “Content-Length” 头域,并会尝试比较新增的 “Etag” 与请求的 “If-None-Match” 值(),若是相同则会修改默认的响应编码 200304finish 会调用 flush 方法将响应数据发送到客户端,接着执行一些资源清理和调用 “模板” 方法。

注:

  1. “Etag”(实体标签, rfc2616)是 HTTP/1.1 协议的一部分,但不是必须的头域(可选),HTTP/1.1 协议规范从未规定过其生成方法。“Etag” 是 HTTP 提供的若干缓存验证机制中的一种,允许客户端进行缓存协商。通常 “Etag” 类似资源内容的指纹,服务端响应时返回资源的 “Etag” 值,客户端可以决定是否缓存这个资源和 “Etag” 值。之后客户端想再请求相同 URL 时,通常会把缓存的 “Etag” 值放在 “Etag/” 字段中发送给服务端。服务端可能在接收请求后比较客户端的 “If-None-Match” 与 服务端 “Etag” 值,如果匹配,就意味着资源未发生变化,服务器就会发送一个 HTTP 304(GET/HEAD) 或者 412(other request methods) 状态码,而不需要再次发送响应消息体。反之,服务端会发送一个包含消息体的完整响应。前面说 “通常”,是因为 HTTP 规范中还定义了 “If-Match”(不匹配时响应 HTTP 412) 字段,rfc2616 上说的很不明确,通常不推荐使用,至少 Tornado 目前仅仅支持 “GET/HEAD” 与 “Etag/If-None-Match”。关于 “If-Match” 的问题,我在 stackoverflow 查到一个资料说是: 不推荐使用,还有一个对 rfc2616 “If-Match” 的总结。

  2. Last-Modified 实体头字段,该字段用于指示资源最后修改时间,在服务端响应时发送给客户端,在之后客户端请求相同 URL 时会把这个字段值放在 If-Modified-Since 或者 If-Unmodified-Since请求头中发送给服务端。服务端将资源当前修改时间与该值进行比较以确定是否响应 HTTP 304。

  3. “Etag” 比 “Last-Modified” 控制的更准确,能够解决一些 “Last-Modified” 无法解决的问题,比如:1. 周期性修改的文件,但内容不改变;2. “Last-Modified” 只能精确到秒;3. 某些时候可能无法准确得到文件的修改时间。

  4. 这里有人做了一个 http headers status 的草图,看图更清晰。

flush

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def flush(self, include_footers=False, callback=None):
"""Flushes the current output buffer to the network.

The ``callback`` argument, if given, can be used for flow control:
it will be run when all flushed data has been written to the socket.
Note that only one flush callback can be outstanding at a time;
if another flush occurs before the previous flush's callback
has been run, the previous callback will be discarded.

.. versionchanged:: 4.0
Now returns a `.Future` if no callback is given.
"""
chunk = b"".join(self._write_buffer)
self._write_buffer = []
# 响应头只会写一次,flush 操作以后再修改的响应头是不会发送到客户端的,但也不会抛异常,
# 这个与其他的一些 web framework 抛异常的处理方式不一样。
if not self._headers_written:
self._headers_written = True
for transform in self._transforms:
self._status_code, self._headers, chunk = \
transform.transform_first_chunk(
self._status_code, self._headers, chunk, include_footers)
# Ignore the chunk and only write the headers for HEAD requests
if self.request.method == "HEAD":
chunk = None

# Finalize the cookie headers (which have been stored in a side
# object so an outgoing cookie could be overwritten before it
# is sent).
if hasattr(self, "_new_cookie"):
for cookie in self._new_cookie.values():
self.add_header("Set-Cookie", cookie.OutputString(None))

# NOTE:在 Keep-Alive 模式下(`Connection: Keep-Alive`),判断消息边界只能通过
# `Content-Length` 或者 `Transfer-Encoding: chunked`,后者仅在 Http/1.1
# 提供。若客户端请求是 Http/1.0,在没有指定 `Content-Length` 头域的情况下,按照
# 目前实现主动多次调用 `flush(include_footers=False)` 方法写数据,
# `write_headers` 中没法支持 chunked,会有问题。看了一下之后的代码,这里已经不依赖
# `self.request.version`,`write_headers` 强制启用 Http/1.1。
start_line = httputil.ResponseStartLine(self.request.version,
self._status_code,
self._reason)
return self.request.connection.write_headers(
start_line, self._headers, chunk, callback=callback)
else:
for transform in self._transforms:
chunk = transform.transform_chunk(chunk, include_footers)
# Ignore the chunk and only write the headers for HEAD requests
if self.request.method != "HEAD":
return self.request.connection.write(chunk, callback=callback)
else:
future = Future()
future.set_result(None)
return future

flush 方法根据是否需要写 headers 来决定调用 Http1Connection 实例的 write_headers 方法或者 write 方法。需要注意的是,响应的 HTTP 版本与请求的 HTTP 版本是一致的,这样 HTTP/1.0 时一次请求多次调用 flush 可能会因为没有提前指定 Content-Length 而导致 “Keep-Alive” 时数据无边界问题(HTTP/1.1 支持分块传输编码便不会有这个问题,在前面分析 Http1Connectionwrite_headers 方法时有解释)。

Cookie 的设置是通过响应头 “Set-Cookie” 实现的,代码中将每一个 cookie 通过 self.add_header("Set-Cookie", cookie.OutputString(None)) 方法加入到响应头 “Set-Cookie”,最终会有多个 “Set-Cookie” 响应头输出到客户端。

还有就是 OutputTransform 的使用,OutputTransform 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class OutputTransform(object):
"""A transform modifies the result of an HTTP request (e.g., GZip encoding)

Applications are not expected to create their own OutputTransforms
or interact with them directly; the framework chooses which transforms
(if any) to apply.
"""
def __init__(self, request):
pass

def transform_first_chunk(self, status_code, headers, chunk, finishing):
return status_code, headers, chunk

def transform_chunk(self, chunk, finishing):
return chunk

OutputTransform 定义了转换响应数据的转换器基类型,因为 HTTP 响应头和消息体格式的差异,该基类型定义了两个方法 transform_first_chunktransform_chunk,分别对响应头和消息体进行数据转换。我们可以自定义实现自己的转化器,在 Application 初始化时传递给它,目前框架默认实现的转换器是 GZipContentEncoding,仅在没有指定任何转换器时根据设置尝试使用,下面是 Application 构造方法的相关代码代码:

1
2
3
4
5
6
7
8
9
10
def __init__(self, handlers=None, default_host="", transforms=None,
**settings):
if transforms is None:
self.transforms = []
if settings.get("compress_response") or settings.get("gzip"):
self.transforms.append(GZipContentEncoding)
else:
self.transforms = transforms

……略……

GZipContentEncoding 实现对响应数据进行 gzip 压缩的功能,一般生产环境下 Tornado 进程不会直接位于请求最前端,所以可能不常使用。通常这个 gzip 压缩功能我都交给 Nginx 去做。

header methods

clear 方法,用来重置响应头,请求处理器在 __init__ 调用该方法初始化响应头:默认的响应头有 “Server”,“Content-Type”,“Date”,状态码是 200。set_default_headers 方法是个模板方法,子类可以实现该方法来为每次请求添加其他的默认响应头。

1
2
3
4
5
6
7
8
9
10
11
def clear(self):
"""Resets all headers and content for this response."""
self._headers = httputil.HTTPHeaders({
"Server": "TornadoServer/%s" % tornado.version,
"Content-Type": "text/html; charset=UTF-8",
"Date": httputil.format_timestamp(time.time()),
})
self.set_default_headers()
self._write_buffer = []
self._status_code = 200
self._reason = httputil.responses[200]

set_header 设置给定响应头,如果给定的 “datetime” 类型的值, Tornado 自动按照 HTTP 要求将其格式化。如果不是 “string”,Tornado 会将其转为 “string”,所有的值都会按照 utf8 编码。

add_header 添加给定的响应头,与 set_header 不同,该方法可以被多次调用而为同一个响应头设置多个值。

clear_header 清除给定的响应头,撤销前一个 “set_header” 调用,但不是撤销 “add_header” 调用(这个说的可能有点绕,具体可以看 HttpHeader 的实现代码。简单来说就是因为 HttpHeader 重写了 __setitem__ 方法,add_header 绕过了重写的 __setitem__ 方法,所以说不能通过 clear_header 中调用 del 操作撤销。)。

set_statusget_status 用于设置和获取响应的状态码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def set_status(self, status_code, reason=None):
"""Sets the status code for our response.

:arg int status_code: Response status code. If ``reason`` is ``None``,
it must be present in `httplib.responses <http.client.responses>`.
:arg string reason: Human-readable reason phrase describing the status
code. If ``None``, it will be filled in from
`httplib.responses <http.client.responses>`.
"""
self._status_code = status_code
if reason is not None:
self._reason = escape.native_str(reason)
else:
try:
self._reason = httputil.responses[status_code]
except KeyError:
raise ValueError("unknown status code %d", status_code)

def get_status(self):
"""Returns the status code for our response."""
return self._status_code

def set_header(self, name, value):
"""Sets the given response header name and value.

If a datetime is given, we automatically format it according to the
HTTP specification. If the value is not a string, we convert it to
a string. All header values are then encoded as UTF-8.
"""
self._headers[name] = self._convert_header_value(value)

def add_header(self, name, value):
"""Adds the given response header and value.

Unlike `set_header`, `add_header` may be called multiple times
to return multiple values for the same header.
"""
self._headers.add(name, self._convert_header_value(value))

def clear_header(self, name):
"""Clears an outgoing header, undoing a previous `set_header` call.

Note that this method does not apply to multi-valued headers
set by `add_header`.
"""
if name in self._headers:
del self._headers[name]

# ASCII 0~31 的字符将视为非法的 Header 字符。后面有提到,比如如果允许 `\r\n` 字符,
# 便可能导致意外的 Header 注入(按照 Http 协议,Header 之间是以 `\r\n` 分割)。
_INVALID_HEADER_CHAR_RE = re.compile(br"[\x00-\x1f]")

def _convert_header_value(self, value):
if isinstance(value, bytes_type):
pass
elif isinstance(value, unicode_type):
value = value.encode('utf-8')
elif isinstance(value, numbers.Integral):
# return immediately since we know the converted value will be safe
return str(value)
elif isinstance(value, datetime.datetime):
return httputil.format_timestamp(value)
else:
raise TypeError("Unsupported header value %r" % value)
# If \n is allowed into the header, it is possible to inject
# additional headers or split the request. Also cap length to
# prevent obviously erroneous values.
if (len(value) > 4000 or
RequestHandler._INVALID_HEADER_CHAR_RE.search(value)):
raise ValueError("Unsafe header value %r", value)
return value

附:http-headers-status 图