| import json |
| import os.path |
| import socket |
| import socketserver |
| import threading |
| from contextlib import closing, contextmanager |
| from http.server import SimpleHTTPRequestHandler |
| from typing import Callable, Generator |
| from urllib.request import urlopen |
|
|
| import h11 |
|
|
|
|
| @contextmanager |
| def socket_server( |
| handler: Callable[..., socketserver.BaseRequestHandler] |
| ) -> Generator[socketserver.TCPServer, None, None]: |
| httpd = socketserver.TCPServer(("127.0.0.1", 0), handler) |
| thread = threading.Thread( |
| target=httpd.serve_forever, kwargs={"poll_interval": 0.01} |
| ) |
| thread.daemon = True |
| try: |
| thread.start() |
| yield httpd |
| finally: |
| httpd.shutdown() |
|
|
|
|
| test_file_path = os.path.join(os.path.dirname(__file__), "data/test-file") |
| with open(test_file_path, "rb") as f: |
| test_file_data = f.read() |
|
|
|
|
| class SingleMindedRequestHandler(SimpleHTTPRequestHandler): |
| def translate_path(self, path: str) -> str: |
| return test_file_path |
|
|
|
|
| def test_h11_as_client() -> None: |
| with socket_server(SingleMindedRequestHandler) as httpd: |
| with closing(socket.create_connection(httpd.server_address)) as s: |
| c = h11.Connection(h11.CLIENT) |
|
|
| s.sendall( |
| c.send( |
| h11.Request( |
| method="GET", target="/foo", headers=[("Host", "localhost")] |
| ) |
| ) |
| ) |
| s.sendall(c.send(h11.EndOfMessage())) |
|
|
| data = bytearray() |
| while True: |
| event = c.next_event() |
| print(event) |
| if event is h11.NEED_DATA: |
| |
| |
| c.receive_data(s.recv(10)) |
| continue |
| if type(event) is h11.Response: |
| assert event.status_code == 200 |
| if type(event) is h11.Data: |
| data += event.data |
| if type(event) is h11.EndOfMessage: |
| break |
| assert bytes(data) == test_file_data |
|
|
|
|
| class H11RequestHandler(socketserver.BaseRequestHandler): |
| def handle(self) -> None: |
| with closing(self.request) as s: |
| c = h11.Connection(h11.SERVER) |
| request = None |
| while True: |
| event = c.next_event() |
| if event is h11.NEED_DATA: |
| |
| |
| c.receive_data(s.recv(10)) |
| continue |
| if type(event) is h11.Request: |
| request = event |
| if type(event) is h11.EndOfMessage: |
| break |
| assert request is not None |
| info = json.dumps( |
| { |
| "method": request.method.decode("ascii"), |
| "target": request.target.decode("ascii"), |
| "headers": { |
| name.decode("ascii"): value.decode("ascii") |
| for (name, value) in request.headers |
| }, |
| } |
| ) |
| s.sendall(c.send(h11.Response(status_code=200, headers=[]))) |
| s.sendall(c.send(h11.Data(data=info.encode("ascii")))) |
| s.sendall(c.send(h11.EndOfMessage())) |
|
|
|
|
| def test_h11_as_server() -> None: |
| with socket_server(H11RequestHandler) as httpd: |
| host, port = httpd.server_address |
| url = "http://{}:{}/some-path".format(host, port) |
| with closing(urlopen(url)) as f: |
| assert f.getcode() == 200 |
| data = f.read() |
| info = json.loads(data.decode("ascii")) |
| print(info) |
| assert info["method"] == "GET" |
| assert info["target"] == "/some-path" |
| assert "urllib" in info["headers"]["user-agent"] |
|
|