test_against_stdlib_http.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import json
  2. import os.path
  3. import socket
  4. import socketserver
  5. import threading
  6. from contextlib import closing, contextmanager
  7. from http.server import SimpleHTTPRequestHandler
  8. from typing import Callable, Generator
  9. from urllib.request import urlopen
  10. import h11
  11. @contextmanager
  12. def socket_server(
  13. handler: Callable[..., socketserver.BaseRequestHandler]
  14. ) -> Generator[socketserver.TCPServer, None, None]:
  15. httpd = socketserver.TCPServer(("127.0.0.1", 0), handler)
  16. thread = threading.Thread(
  17. target=httpd.serve_forever, kwargs={"poll_interval": 0.01}
  18. )
  19. thread.daemon = True
  20. try:
  21. thread.start()
  22. yield httpd
  23. finally:
  24. httpd.shutdown()
  25. test_file_path = os.path.join(os.path.dirname(__file__), "data/test-file")
  26. with open(test_file_path, "rb") as f:
  27. test_file_data = f.read()
  28. class SingleMindedRequestHandler(SimpleHTTPRequestHandler):
  29. def translate_path(self, path: str) -> str:
  30. return test_file_path
  31. def test_h11_as_client() -> None:
  32. with socket_server(SingleMindedRequestHandler) as httpd:
  33. with closing(socket.create_connection(httpd.server_address)) as s:
  34. c = h11.Connection(h11.CLIENT)
  35. s.sendall(
  36. c.send( # type: ignore[arg-type]
  37. h11.Request(
  38. method="GET", target="/foo", headers=[("Host", "localhost")]
  39. )
  40. )
  41. )
  42. s.sendall(c.send(h11.EndOfMessage())) # type: ignore[arg-type]
  43. data = bytearray()
  44. while True:
  45. event = c.next_event()
  46. print(event)
  47. if event is h11.NEED_DATA:
  48. # Use a small read buffer to make things more challenging
  49. # and exercise more paths :-)
  50. c.receive_data(s.recv(10))
  51. continue
  52. if type(event) is h11.Response:
  53. assert event.status_code == 200
  54. if type(event) is h11.Data:
  55. data += event.data
  56. if type(event) is h11.EndOfMessage:
  57. break
  58. assert bytes(data) == test_file_data
  59. class H11RequestHandler(socketserver.BaseRequestHandler):
  60. def handle(self) -> None:
  61. with closing(self.request) as s:
  62. c = h11.Connection(h11.SERVER)
  63. request = None
  64. while True:
  65. event = c.next_event()
  66. if event is h11.NEED_DATA:
  67. # Use a small read buffer to make things more challenging
  68. # and exercise more paths :-)
  69. c.receive_data(s.recv(10))
  70. continue
  71. if type(event) is h11.Request:
  72. request = event
  73. if type(event) is h11.EndOfMessage:
  74. break
  75. assert request is not None
  76. info = json.dumps(
  77. {
  78. "method": request.method.decode("ascii"),
  79. "target": request.target.decode("ascii"),
  80. "headers": {
  81. name.decode("ascii"): value.decode("ascii")
  82. for (name, value) in request.headers
  83. },
  84. }
  85. )
  86. s.sendall(c.send(h11.Response(status_code=200, headers=[]))) # type: ignore[arg-type]
  87. s.sendall(c.send(h11.Data(data=info.encode("ascii"))))
  88. s.sendall(c.send(h11.EndOfMessage()))
  89. def test_h11_as_server() -> None:
  90. with socket_server(H11RequestHandler) as httpd:
  91. host, port = httpd.server_address
  92. url = "http://{}:{}/some-path".format(host, port)
  93. with closing(urlopen(url)) as f:
  94. assert f.getcode() == 200
  95. data = f.read()
  96. info = json.loads(data.decode("ascii"))
  97. print(info)
  98. assert info["method"] == "GET"
  99. assert info["target"] == "/some-path"
  100. assert "urllib" in info["headers"]["user-agent"]