| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | from ._events import Data, EndOfMessage |
| | from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER |
| | from ._util import LocalProtocolError |
| |
|
| | __all__ = ["WRITERS"] |
| |
|
| |
|
| | def write_headers(headers, write): |
| | |
| | |
| | |
| | raw_items = headers._full_items |
| | for raw_name, name, value in raw_items: |
| | if name == b"host": |
| | write(b"%s: %s\r\n" % (raw_name, value)) |
| | for raw_name, name, value in raw_items: |
| | if name != b"host": |
| | write(b"%s: %s\r\n" % (raw_name, value)) |
| | write(b"\r\n") |
| |
|
| |
|
| | def write_request(request, write): |
| | if request.http_version != b"1.1": |
| | raise LocalProtocolError("I only send HTTP/1.1") |
| | write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target)) |
| | write_headers(request.headers, write) |
| |
|
| |
|
| | |
| | def write_any_response(response, write): |
| | if response.http_version != b"1.1": |
| | raise LocalProtocolError("I only send HTTP/1.1") |
| | status_bytes = str(response.status_code).encode("ascii") |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason)) |
| | write_headers(response.headers, write) |
| |
|
| |
|
| | class BodyWriter: |
| | def __call__(self, event, write): |
| | if type(event) is Data: |
| | self.send_data(event.data, write) |
| | elif type(event) is EndOfMessage: |
| | self.send_eom(event.headers, write) |
| | else: |
| | assert False |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | class ContentLengthWriter(BodyWriter): |
| | def __init__(self, length): |
| | self._length = length |
| |
|
| | def send_data(self, data, write): |
| | self._length -= len(data) |
| | if self._length < 0: |
| | raise LocalProtocolError("Too much data for declared Content-Length") |
| | write(data) |
| |
|
| | def send_eom(self, headers, write): |
| | if self._length != 0: |
| | raise LocalProtocolError("Too little data for declared Content-Length") |
| | if headers: |
| | raise LocalProtocolError("Content-Length and trailers don't mix") |
| |
|
| |
|
| | class ChunkedWriter(BodyWriter): |
| | def send_data(self, data, write): |
| | |
| | |
| | if not data: |
| | return |
| | write(b"%x\r\n" % len(data)) |
| | write(data) |
| | write(b"\r\n") |
| |
|
| | def send_eom(self, headers, write): |
| | write(b"0\r\n") |
| | write_headers(headers, write) |
| |
|
| |
|
| | class Http10Writer(BodyWriter): |
| | def send_data(self, data, write): |
| | write(data) |
| |
|
| | def send_eom(self, headers, write): |
| | if headers: |
| | raise LocalProtocolError("can't send trailers to HTTP/1.0 client") |
| | |
| | |
| |
|
| |
|
| | WRITERS = { |
| | (CLIENT, IDLE): write_request, |
| | (SERVER, IDLE): write_any_response, |
| | (SERVER, SEND_RESPONSE): write_any_response, |
| | SEND_BODY: { |
| | "chunked": ChunkedWriter, |
| | "content-length": ContentLengthWriter, |
| | "http/1.0": Http10Writer, |
| | }, |
| | } |
| |
|