Skip to content

Commit 73905ad

Browse files
Migrate http proxy tests to pproxy (#199)
* Replaced mitmproxy by pproxy for proxy tests * Rebased onto master branch HEAD. Removed unused fixtures. Co-authored-by: Florimond Manca <florimond.manca@gmail.com> * Apply suggestions from code review Co-authored-by: Florimond Manca <florimond.manca@gmail.com> * Apply suggestions from code review Co-authored-by: Florimond Manca <florimond.manca@gmail.com> Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
1 parent f7ccbd3 commit 73905ad

5 files changed

Lines changed: 38 additions & 96 deletions

File tree

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ flake8==3.8.4
2222
flake8-bugbear==20.1.4
2323
flake8-pie==0.6.1
2424
isort==5.5.4
25-
mitmproxy==5.2
2625
mypy==0.782
26+
pproxy==2.3.5
2727
pytest==6.1.0
2828
pytest-trio==0.6.0
2929
pytest-cov==2.10.1
30-
trustme==0.6.0
3130
uvicorn==0.12.1

tests/async_tests/test_interfaces.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import platform
2-
import ssl
32

43
import pytest
54

@@ -217,7 +216,6 @@ async def test_http_request_local_address(backend: str, server: Server) -> None:
217216
@pytest.mark.anyio
218217
async def test_proxy_https_requests(
219218
proxy_server: URL,
220-
ca_ssl_context: ssl.SSLContext,
221219
proxy_mode: str,
222220
http2: bool,
223221
https_server: Server,
@@ -229,7 +227,6 @@ async def test_proxy_https_requests(
229227
async with httpcore.AsyncHTTPProxy(
230228
proxy_server,
231229
proxy_mode=proxy_mode,
232-
ssl_context=ca_ssl_context,
233230
max_connections=max_connections,
234231
http2=http2,
235232
) as http:

tests/conftest.py

Lines changed: 6 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,27 @@
1-
import asyncio
21
import contextlib
32
import os
4-
import ssl
53
import threading
64
import time
75
import typing
86

97
import pytest
10-
import trustme
118
import uvicorn
12-
from mitmproxy import options, proxy
13-
from mitmproxy.tools.dump import DumpMaster
149

1510
from httpcore._types import URL
1611

17-
from .utils import Server
12+
from .utils import Server, http_proxy_server
1813

19-
PROXY_HOST = "127.0.0.1"
20-
PROXY_PORT = 8080
2114
SERVER_HOST = "example.org"
2215
HTTPS_SERVER_URL = "https://example.org"
2316

2417

25-
class RunNotify:
26-
"""A mitmproxy addon wrapping an event to notify us when the server is running."""
27-
28-
def __init__(self) -> None:
29-
self.started = threading.Event()
30-
31-
def running(self) -> None:
32-
self.started.set()
33-
34-
35-
class ProxyWrapper(threading.Thread):
36-
"""Runs an mitmproxy in a separate thread."""
37-
38-
def __init__(self, host: str, port: int, **kwargs: typing.Any) -> None:
39-
self.host = host
40-
self.port = port
41-
self.options = kwargs
42-
super().__init__()
43-
self.notify = RunNotify()
44-
45-
def run(self) -> None:
46-
# mitmproxy uses asyncio internally but the default loop policy
47-
# will only create event loops for the main thread, create one
48-
# as part of the thread startup
49-
asyncio.set_event_loop(asyncio.new_event_loop())
50-
opts = options.Options(
51-
listen_host=self.host, listen_port=self.port, **self.options
52-
)
53-
pconf = proxy.config.ProxyConfig(opts)
54-
55-
self.master = DumpMaster(opts)
56-
self.master.server = proxy.server.ProxyServer(pconf)
57-
self.master.addons.add(self.notify)
58-
self.master.run()
59-
60-
def join(self, timeout: float = None) -> None:
61-
self.master.shutdown()
62-
super().join()
63-
64-
65-
@pytest.fixture(scope="session")
66-
def cert_authority() -> trustme.CA:
67-
return trustme.CA()
68-
69-
70-
@pytest.fixture()
71-
def ca_ssl_context(cert_authority: trustme.CA) -> ssl.SSLContext:
72-
ctx = ssl.create_default_context()
73-
cert_authority.configure_trust(ctx)
74-
return ctx
75-
76-
77-
@pytest.fixture(scope="session")
78-
def example_org_cert(cert_authority: trustme.CA) -> trustme.LeafCert:
79-
return cert_authority.issue_cert("example.org")
80-
81-
8218
@pytest.fixture(scope="session")
83-
def example_org_cert_path(example_org_cert: trustme.LeafCert) -> typing.Iterator[str]:
84-
with example_org_cert.private_key_and_cert_chain_pem.tempfile() as tmp:
85-
yield tmp
19+
def proxy_server() -> typing.Iterator[URL]:
20+
proxy_host = "127.0.0.1"
21+
proxy_port = 8080
8622

87-
88-
@pytest.fixture()
89-
def proxy_server(example_org_cert_path: str) -> typing.Iterator[URL]:
90-
"""Starts a proxy server on a different thread and yields its origin tuple.
91-
92-
The server is configured to use a trustme CA and key, this will allow our
93-
test client to make HTTPS requests when using the ca_ssl_context fixture
94-
above.
95-
96-
Note this is only required because mitmproxy's main purpose is to analyse
97-
traffic. Other proxy servers do not need this but mitmproxy is easier to
98-
integrate in our tests.
99-
"""
100-
try:
101-
thread = ProxyWrapper(PROXY_HOST, PROXY_PORT, certs=[example_org_cert_path])
102-
thread.start()
103-
thread.notify.started.wait()
104-
yield (b"http", PROXY_HOST.encode(), PROXY_PORT, b"/")
105-
finally:
106-
thread.join()
23+
with http_proxy_server(proxy_host, proxy_port) as proxy_url:
24+
yield proxy_url
10725

10826

10927
class UvicornServer(uvicorn.Server):

tests/sync_tests/test_interfaces.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import platform
2-
import ssl
32

43
import pytest
54

@@ -217,7 +216,6 @@ def test_http_request_local_address(backend: str, server: Server) -> None:
217216

218217
def test_proxy_https_requests(
219218
proxy_server: URL,
220-
ca_ssl_context: ssl.SSLContext,
221219
proxy_mode: str,
222220
http2: bool,
223221
https_server: Server,
@@ -229,7 +227,6 @@ def test_proxy_https_requests(
229227
with httpcore.SyncHTTPProxy(
230228
proxy_server,
231229
proxy_mode=proxy_mode,
232-
ssl_context=ca_ssl_context,
233230
max_connections=max_connections,
234231
http2=http2,
235232
) as http:

tests/utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import contextlib
2+
import socket
3+
import subprocess
4+
import time
15
from typing import Tuple
26

37
import sniffio
@@ -11,6 +15,17 @@ def lookup_sync_backend():
1115
return "sync"
1216

1317

18+
def _wait_can_connect(host: str, port: int):
19+
while True:
20+
try:
21+
sock = socket.create_connection((host, port))
22+
except ConnectionRefusedError:
23+
time.sleep(0.25)
24+
else:
25+
sock.close()
26+
break
27+
28+
1429
class Server:
1530
"""
1631
Represents the server we're testing against.
@@ -27,3 +42,19 @@ def netloc(self) -> Tuple[bytes, int]:
2742
@property
2843
def host_header(self) -> Tuple[bytes, bytes]:
2944
return (b"host", self._host.encode("utf-8"))
45+
46+
47+
@contextlib.contextmanager
48+
def http_proxy_server(proxy_host: str, proxy_port: int):
49+
50+
proc = None
51+
try:
52+
command = ["pproxy", "-l", f"http://{proxy_host}:{proxy_port}/"]
53+
proc = subprocess.Popen(command)
54+
55+
_wait_can_connect(proxy_host, proxy_port)
56+
57+
yield b"http", proxy_host.encode(), proxy_port, b"/"
58+
finally:
59+
if proc is not None:
60+
proc.kill()

0 commit comments

Comments
 (0)