def test_header_happy_case(): parser = HTTPHeaderParser() rst = parser.feed(Ref(HTTP_10_GET())) assert isinstance(rst, HTTPRequestHeader) assert rst.method == b'GET' assert rst.location.scheme == 'http' assert rst.location.netloc == 'example.com' assert rst.location.path == '/path' assert rst.location.query == 'key=value' assert rst.version == b'HTTP/1.0' assert rst.headers == {b'Header': (0, b'header-val')}
def __init__(self, client_socket, client_address, bfc: BFCNode, ev_server: EventServer): self._client_socket = client_socket self._client_address = client_address self._client_recv_buf = b"" """Data RECEIVED from the client.""" self._client_send_buf = b"" """Data to SEND to the client.""" self._client_writable: bool = False """If the client is ready to accept data.""" self._state = _WorkerState.CREATED self._ev_server = ev_server self._http_header_parser = HTTPHeaderParser() self._http_body_parser: HTTPBodyParser = None self._cur_session: _WorkerSession = None self._bfc = bfc
class _Worker(EventConsumer): """Handles a worker connection.""" def __init__(self, client_socket, client_address, bfc: BFCNode, ev_server: EventServer): self._client_socket = client_socket self._client_address = client_address self._client_recv_buf = b"" """Data RECEIVED from the client.""" self._client_send_buf = b"" """Data to SEND to the client.""" self._client_writable: bool = False """If the client is ready to accept data.""" self._state = _WorkerState.CREATED self._ev_server = ev_server self._http_header_parser = HTTPHeaderParser() self._http_body_parser: HTTPBodyParser = None self._cur_session: _WorkerSession = None self._bfc = bfc def start(self): # Nothing to do here _log.debug("Worker starts on (%s:%s) <-> (%s:%s)", *self._bindings()) def events(self): yield self._client_socket.fileno( ), select.EPOLLIN | select.EPOLLOUT | select.EPOLLET def terminate(self): """Abort and do the cleanups.""" _log.debug("Terminating worker on (%s:%s) <-> (%s:%s)", *self._bindings()) self._ev_server.unregister(self) if self._cur_session: self._cur_session.end() self._client_socket.close() def queue_send(self, s: bytes = b"") -> int: """ Try to send data to the client. Returns the number of bytes sent. All data will be queued for sending later nonetheless. return: Number of bytes sent. """ total_byte_sent = 0 self._client_send_buf += s try: while self._client_send_buf and self._client_writable: byte_sent = self._client_socket.send(self._client_send_buf) total_byte_sent += byte_sent self._client_send_buf = self._client_send_buf[byte_sent:] except socket.error as e: if e.errno != errno.EAGAIN: raise e else: self._client_writable = False return total_byte_sent def _bindings(self) -> List[Union[str, int]]: addr = self._client_socket.getsockname() return [ addr[0], addr[1], self._client_address[0], self._client_address[1] ] def _handle_created(self, ev: int): """Event handler for state == CREATED""" if not ev & select.EPOLLIN: # We care only when we have something to parse. return ref_client_recv_buf = Ref(self._client_recv_buf) parse_result = self._http_header_parser.feed(ref_client_recv_buf) self._client_recv_buf = ref_client_recv_buf.v if parse_result == HTTPParseStatus.PARTIAL: return elif parse_result == HTTPParseStatus.ERROR: _log.debug("Worker got illegal input on (%s:%s) <-> (%s:%s)", *self._bindings()) self.terminate() elif isinstance(parse_result, HTTPRequestHeader): header = parse_result print('FINDME2', header.location) if header.method != b"CONNECT": # if we are not doing HTTP CONNECT, then the header consumed is also a part of the request. (after # conversion to non-proxy header) header.unproxify() self._client_recv_buf = header.reconstruct( ) + self._client_recv_buf else: self._client_send_buf = b'HTTP/1.1 200 OK\r\n\r\n' # This is the best we can do. Because HTTPS should use CONNECT anyway. port = header.location.port if header.location.port else 80 self._state = _WorkerState.RELAYING self._http_body_parser = HTTPBodyParser(header) self._cur_session = _WorkerSession( (header.location.hostname, port), self, self._bfc) self._cur_session.start() self._handle_relaying(ev) def _handle_relaying(self, ev: int): """Event handler for state == RELAYING""" if ev & select.EPOLLIN: # I got some data from the client, I want to send it to the BFC. # but first, I need to determine if all the data belong to the current session. parse_result = self._http_body_parser.feed(self._client_recv_buf) if parse_result == HTTPParseStatus.PARTIAL: print('FINEME3', self._client_recv_buf) self._cur_session.send(self._client_recv_buf) self._client_recv_buf = b"" elif parse_result == HTTPParseStatus.ERROR: self.terminate() elif isinstance(parse_result, int): # part of the buffer is the tail of the current session. self._cur_session.send(self._client_recv_buf[:parse_result]) # the rest belongs to the coming new session. self._client_recv_buf = self._client_recv_buf[parse_result:] self._cur_session.end() self._state = _WorkerState.CREATED self._handle_created(ev) if ev & select.EPOLLOUT: # I can write to the client, try to send the buffer I have self.queue_send() def handle_event(self, fileno: int, ev: int): if ev & select.EPOLLIN: try: while True: r = self._client_socket.recv(1024) if not r: # died self.terminate() break self._client_recv_buf += r except socket.error as e: if e.errno != errno.EAGAIN: raise e print('FINDME', self._client_recv_buf) if ev & select.EPOLLOUT: self._client_writable = True if self._state == _WorkerState.CREATED: self._handle_created(ev) elif self._state == _WorkerState.RELAYING: self._handle_relaying(ev)
def test_header_cut_input(): parser = HTTPHeaderParser() ref = Ref(HTTP_10_GET() + b"extra data") rst = parser.feed(ref) assert isinstance(rst, HTTPRequestHeader) assert ref.v == b'extra data'
def test_header_partial_input(s): parser = HTTPHeaderParser() rst = parser.feed(Ref(s)) assert isinstance(rst, HTTPParseStatus) assert rst == HTTPParseStatus.PARTIAL
def test_header_bad_input(): parser = HTTPHeaderParser() rst = parser.feed(Ref(b"a b c d e f g")) assert isinstance(rst, HTTPParseStatus) assert rst == HTTPParseStatus.ERROR
def test_unproxyify(): rst = HTTPHeaderParser().feed(Ref(HTTP_10_GET())) rst.unproxify() assert rst.headers[b'Host'][1] == b'example.com' assert rst.uri == b'/path?key=value'
def test_reconstruct_header(): s = b"GET http://example.com/path?key=value HTTP/1.0\r\n2Header: header-val1\r\n1Header: header-val1\r\n\r\n" assert HTTPHeaderParser().feed(Ref(s)).reconstruct() == s