Files
wehub-resource-sync adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

313 lines
11 KiB
Python

"""Contains tests for networking.py and app.py"""
import http.server
import os
import socket
import socketserver
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
import requests
import uvicorn
from fastapi import FastAPI
from fastapi.testclient import TestClient
from gradio import Interface, networking
from gradio.route_utils import API_PREFIX
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
class TestInterfaceErrors:
def test_processing_error(self):
io = Interface(lambda x: 1 / x, "number", "number", api_name="predict")
app, _, _ = io.launch(show_error=True, prevent_thread_lock=True)
client = TestClient(app)
response = client.post(
f"{API_PREFIX}/api/predict/", json={"data": [0], "fn_index": 0}
)
assert response.status_code == 500
assert "error" in response.json()
io.close()
def test_validation_error(self):
io = Interface(lambda x: 1 / x, "number", "number")
app, _, _ = io.launch(show_error=True, prevent_thread_lock=True)
client = TestClient(app)
response = client.post(f"{API_PREFIX}/api/predict/", json={"fn_index": [0]})
assert response.status_code == 422
io.close()
class TestURLs:
def test_url_ok(self):
res = networking.url_ok("https://www.gradio.app")
assert res
def test_start_server_app_kwargs():
"""
Test that start_server accepts app_kwargs and they're propagated to FastAPI.
"""
io = Interface(lambda x: x, "number", "number")
app, _, _ = io.launch(
show_error=True,
prevent_thread_lock=True,
app_kwargs={
"docs_url": f"{API_PREFIX}/docs",
},
)
client = TestClient(app)
assert client.get(f"{API_PREFIX}/docs").status_code == 200
io.close()
def test_reverse_proxy():
import gradio as gr
demo = Interface(lambda x: x, "text", "text")
app = FastAPI()
gr.mount_gradio_app(app, demo, path="/gradio")
def is_port_available(port):
try:
s = socket.socket()
s.bind(("127.0.0.1", target_port))
s.close()
return True
except OSError:
return False
for target_port in range(8000, 8100):
if is_port_available(target_port):
break
else:
raise ValueError("No available ports")
for proxy_port in range(18000, 18100):
if is_port_available(proxy_port):
break
else:
raise ValueError("No available ports")
target_host = "localhost"
class HTMLReverseProxyHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self): # noqa
self.handle_request()
def do_HEAD(self): # noqa
self.handle_request()
def do_POST(self): # noqa
self.handle_request()
def do_PUT(self): # noqa
self.handle_request()
def do_DELETE(self): # noqa
self.handle_request()
def do_OPTIONS(self): # noqa
self.handle_request()
def do_PATCH(self): # noqa
self.handle_request()
def handle_request(self):
# Store the original path for reference and content rewriting
original_path = self.path
# Extract the path without query string
request_path = original_path.split("?", 1)[0]
# Check if the path is for a JS module
is_js_request = request_path.endswith(".js")
# Forward the full original path to the target server
target_url = f"http://{target_host}:{target_port}{original_path}"
# Get the request body if present
content_length = int(self.headers.get("Content-Length", 0))
request_body = None
if content_length > 0:
request_body = self.rfile.read(content_length)
# Forward headers (excluding hop-by-hop headers)
headers = {}
for key, value in self.headers.items():
if key.lower() not in (
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailers",
"transfer-encoding",
"upgrade",
"host",
):
headers[key] = value
# Set the correct host header
headers["Host"] = f"{target_host}:{target_port}"
# Add X-Forwarded headers for better compatibility
headers["X-Forwarded-For"] = self.client_address[0]
headers["X-Forwarded-Host"] = self.headers.get("Host", "")
headers["X-Forwarded-Proto"] = "http"
try:
# Create and send the request
req = urllib.request.Request(
url=target_url,
data=request_body,
headers=headers,
method=self.command,
)
# Get the response
with urllib.request.urlopen(req) as response:
# Get all response headers
response_headers = dict(response.getheaders())
content_type = response_headers.get("Content-Type", "")
# Send response status code
self.send_response(response.status)
# Special handling for JavaScript files
if is_js_request and "javascript" not in content_type.lower():
self.send_header("Content-Type", "application/javascript")
# For non-JS files, use the original content type
elif "Content-Type" in response_headers:
self.send_header("Content-Type", content_type)
# Forward all other headers
for key, value in response_headers.items():
if key.lower() not in (
"content-type",
"content-length",
"connection",
"keep-alive",
"transfer-encoding",
):
self.send_header(key, value)
# Add additional headers for security and transparency
self.send_header("X-Proxy-By", "Python-HTML-Rewriting-Proxy")
self.end_headers()
# Read the response body
response_data = response.read()
# Handle HTML content rewriting
if "text/html" in content_type.lower():
try:
html_content = response_data.decode("utf-8")
response_data = html_content.encode("utf-8")
except Exception as e:
print(f"Error processing HTML: {str(e)}")
# Send the response body (potentially modified)
self.wfile.write(response_data)
except urllib.error.HTTPError as e:
# Special handling for JS requests that get errors
if is_js_request:
self.send_response(e.code)
self.send_header("Content-Type", "application/javascript")
self.end_headers()
self.wfile.write(f"/* Error: {e.code} */".encode())
else:
# Normal error handling
self.send_response(e.code)
for key, value in e.headers.items():
if key.lower() not in (
"connection",
"keep-alive",
"transfer-encoding",
):
self.send_header(key, value)
self.end_headers()
self.wfile.write(e.read())
except urllib.error.URLError as e:
# Special handling for JS requests
if is_js_request:
self.send_response(502)
self.send_header("Content-Type", "application/javascript")
self.end_headers()
self.wfile.write(
f"/* Error connecting to server: {e.reason} */".encode()
)
else:
# Normal error handling
self.send_response(502) # Bad Gateway
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(
f"Error connecting to upstream server: {e.reason}".encode()
)
except Exception as e:
# Special handling for JS requests
if is_js_request:
self.send_response(500)
self.send_header("Content-Type", "application/javascript")
self.end_headers()
self.wfile.write(f"/* Internal proxy error: {str(e)} */".encode())
else:
# Normal error handling
self.send_response(500)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(f"Proxy error: {str(e)}".encode())
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(
("localhost", proxy_port), HTMLReverseProxyHandler
)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
config = uvicorn.Config(app, port=target_port, loop="asyncio")
uvicorn_server = uvicorn.Server(config)
uvicorn_thread = threading.Thread(target=uvicorn_server.run)
uvicorn_thread.start()
try:
attempts = 5
status_code = None
while attempts > 0 and status_code != 200:
attempts -= 1
try:
response = requests.get(
f"http://localhost:{target_port}/gradio/", timeout=1
)
status_code = response.status_code
except Exception:
time.sleep(1)
if attempts == 0:
raise TimeoutError("Server did not start in time")
response = requests.get(f"http://localhost:{proxy_port}/gradio/")
assert response.status_code == 200
response = requests.get(f"http://localhost:{proxy_port}/gradio/config")
assert response.status_code == 200
json = response.json()
assert json["root"] == f"http://localhost:{proxy_port}/gradio"
finally:
server.shutdown()
server.server_close()
uvicorn_server.should_exit = True
uvicorn_thread.join()