-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support http proxies for websockets (#16326)
- Loading branch information
1 parent
547d022
commit 40590a5
Showing
12 changed files
with
262 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# This is a simple test to ensure we can make a websocket connection through a proxy server. It sets up a | ||
# simple server and a squid proxy server. The proxy server is inaccessible from the host machine, only the proxy | ||
# so we can confirm the proxy is actually working. | ||
|
||
name: Proxy Test | ||
on: | ||
pull_request: | ||
paths: | ||
- .github/workflows/proxy-test.yaml | ||
- scripts/proxy-test/* | ||
- "src/prefect/events/clients.py" | ||
- requirements.txt | ||
- requirements-client.txt | ||
- requirements-dev.txt | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- .github/workflows/proxy-test.yaml | ||
- scripts/proxy-test/* | ||
- "src/prefect/events/clients.py" | ||
- requirements.txt | ||
- requirements-client.txt | ||
- requirements-dev.txt | ||
|
||
jobs: | ||
proxy-test: | ||
name: Proxy Test | ||
timeout-minutes: 10 | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
persist-credentials: false | ||
fetch-depth: 0 | ||
|
||
- name: Set up Python 3.10 | ||
uses: actions/setup-python@v5 | ||
id: setup_python | ||
with: | ||
python-version: "3.10" | ||
|
||
- name: Create Docker networks | ||
run: | | ||
docker network create internal_net --internal | ||
docker network create external_net | ||
- name: Start API server container | ||
working-directory: scripts/proxy-test | ||
run: | | ||
docker build -t api-server . | ||
docker run -d --network internal_net --name server api-server | ||
- name: Start Squid Proxy container | ||
run: | | ||
docker run -d \ | ||
--network internal_net \ | ||
--network external_net \ | ||
-p 3128:3128 \ | ||
-v $(pwd)/scripts/proxy-test/squid.conf:/etc/squid/squid.conf \ | ||
--name proxy \ | ||
ubuntu/squid | ||
- name: Install Dependencies | ||
run: | | ||
python -m pip install -U uv | ||
uv pip install --upgrade --system . | ||
- name: Run Proxy Tests | ||
env: | ||
HTTP_PROXY: http://localhost:3128 | ||
HTTPS_PROXY: http://localhost:3128 | ||
run: python scripts/proxy-test/client.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
FROM python:3.11-slim | ||
|
||
WORKDIR /app | ||
|
||
COPY requirements.txt . | ||
RUN pip install uv | ||
RUN uv pip install --no-cache-dir --system -r requirements.txt | ||
|
||
COPY server.py . | ||
|
||
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
This is a simple test to ensure we can make a websocket connection through a proxy server. It sets up a | ||
simple server and a squid proxy server. The proxy server is inaccessible from the host machine, so we | ||
can confirm the proxy connection is working. | ||
|
||
``` | ||
$ uv pip install -r requirements.txt | ||
$ docker compose up --build | ||
$ python client.py | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import asyncio | ||
import os | ||
|
||
from prefect.events.clients import websocket_connect | ||
|
||
PROXY_URL = "http://localhost:3128" | ||
WS_SERVER_URL = "ws://server:8000/ws" | ||
|
||
|
||
async def test_websocket_proxy_with_compat(): | ||
"""WebSocket through proxy with proxy compatibility code - should work""" | ||
os.environ["HTTP_PROXY"] = PROXY_URL | ||
|
||
async with websocket_connect(WS_SERVER_URL) as websocket: | ||
message = "Hello!" | ||
await websocket.send(message) | ||
response = await websocket.recv() | ||
print("Response: ", response) | ||
assert response == f"Server received: {message}" | ||
|
||
|
||
async def main(): | ||
print("Testing WebSocket through proxy with compatibility code") | ||
await test_websocket_proxy_with_compat() | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
services: | ||
server: | ||
build: . | ||
networks: | ||
- internal_net | ||
|
||
forward_proxy: | ||
image: ubuntu/squid | ||
ports: | ||
- "3128:3128" | ||
volumes: | ||
- ./squid.conf:/etc/squid/squid.conf | ||
networks: | ||
- internal_net | ||
- external_net | ||
|
||
networks: | ||
internal_net: | ||
internal: true | ||
external_net: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
fastapi==0.111.1 | ||
uvicorn==0.28.1 | ||
uv==0.5.7 | ||
websockets==13.1 | ||
python-socks==2.5.3 | ||
httpx==0.28.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from fastapi import FastAPI, WebSocket | ||
|
||
app = FastAPI() | ||
|
||
|
||
@app.websocket("/ws") | ||
async def websocket_endpoint(websocket: WebSocket): | ||
await websocket.accept() | ||
async for data in websocket.iter_text(): | ||
await websocket.send_text(f"Server received: {data}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
http_port 3128 | ||
acl CONNECT method CONNECT | ||
acl SSL_ports port 443 8000 | ||
http_access allow CONNECT SSL_ports | ||
http_access allow all |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from unittest.mock import Mock | ||
|
||
from prefect.events.clients import WebsocketProxyConnect | ||
|
||
|
||
def test_init_ws_without_proxy(): | ||
client = WebsocketProxyConnect("ws://example.com") | ||
assert client.uri == "ws://example.com" | ||
assert client._host == "example.com" | ||
assert client._port == 80 | ||
assert client._proxy is None | ||
|
||
|
||
def test_init_wss_without_proxy(): | ||
client = WebsocketProxyConnect("wss://example.com") | ||
assert client.uri == "wss://example.com" | ||
assert client._host == "example.com" | ||
assert client._port == 443 | ||
assert "server_hostname" in client._kwargs | ||
assert client._proxy is None | ||
|
||
|
||
def test_init_ws_with_proxy(monkeypatch): | ||
monkeypatch.setenv("HTTP_PROXY", "http://proxy:3128") | ||
mock_proxy = Mock() | ||
monkeypatch.setattr("prefect.events.clients.Proxy", mock_proxy) | ||
|
||
client = WebsocketProxyConnect("ws://example.com") | ||
|
||
mock_proxy.from_url.assert_called_once_with("http://proxy:3128") | ||
assert client._proxy is not None | ||
|
||
|
||
def test_init_wss_with_proxy(monkeypatch): | ||
monkeypatch.setenv("HTTPS_PROXY", "https://proxy:3128") | ||
mock_proxy = Mock() | ||
monkeypatch.setattr("prefect.events.clients.Proxy", mock_proxy) | ||
|
||
client = WebsocketProxyConnect("wss://example.com") | ||
|
||
mock_proxy.from_url.assert_called_once_with("https://proxy:3128") | ||
assert client._proxy is not None |