classHTTPConnection(object): """Applications use this interface to write their responses. .. versionadded:: 4.0 """ defwrite_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()
defwrite(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()
deffinish(self): """Indicates that the last body data has been written. """ raise NotImplementedError()
defwrite_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'notin headers and 'Transfer-Encoding'notin 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 != 304and # No need to chunk the output if a Content-Length is specified. 'Content-Length'notin headers and # Applications are discouraged from touching Transfer-Encoding, # but if they do, leave it alone. 'Transfer-Encoding'notin 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: ifb'\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 isnotNone: 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
defwrite(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 isnotNone: 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_format_chunk(self, chunk): if self._expected_content_remaining isnotNone: 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
deffinish(self): """Implements `.HTTPConnection.finish`.""" if (self._expected_content_remaining isnotNoneand self._expected_content_remaining != 0and 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: ifnot 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. ifnot 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 isNone: 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() # 服务端不需要支持长连接时,执行关闭操作 ifnot 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) ifnot 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 isnotNone: self.stream.set_close_callback(None)
defclose(self): if self.stream isnotNone: self.stream.close() self._clear_callbacks() ifnot self._finish_future.done(): self._finish_future.set_result(None)
deffinish(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 isnotNone: self.write(chunk)
# Automatically support ETags and add the Content-Length header if # we have not flushed any content yet. ifnot self._headers_written: if (self._status_code == 200and self.request.method in ("GET", "HEAD") and "Etag"notin self._headers): self.set_etag_header() if self.check_etag_header(): self._write_buffer = [] self.set_status(304) if self._status_code == 304: assertnot self._write_buffer, "Cannot send body with 304" self._clear_headers_for_304() elif"Content-Length"notin 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
defflush(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 抛异常的处理方式不一样。 ifnot 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
classOutputTransform(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
defset_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 isnotNone: self._reason = escape.native_str(reason) else: try: self._reason = httputil.responses[status_code] except KeyError: raise ValueError("unknown status code %d", status_code)
defget_status(self): """Returns the status code for our response.""" return self._status_code
defset_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)
defadd_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))
defclear_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]
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) > 4000or RequestHandler._INVALID_HEADER_CHAR_RE.search(value)): raise ValueError("Unsafe header value %r", value) return value