video_platform / tabs /http_proxy.py
qgyd2021's picture
update
d6c74be
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
HTTP 代理 Tab
提供一个通用的 HTTP 请求代理,既支持在 Gradio 页面上手动调试,
也可以通过 Gradio 的 API 从外部代码调用,例如:
# 通过 gradio_client 调用
from gradio_client import Client
client = Client("http://127.0.0.1:7860/")
status, headers, body = client.predict(
url="https://httpbin.org/get",
method="GET",
headers="{}",
params="{}",
body="",
timeout=30.0,
follow_redirects=True,
verify_ssl=True,
api_name="/http_proxy",
)
# 通过 HTTP 调用 gradio 内置 API
POST http://127.0.0.1:7860/gradio_api/call/http_proxy
body: {"data": ["https://httpbin.org/get", "GET", "{}", "{}", "", 30.0, true, true]}
cookies 用法:
没有单独的 cookies 字段, 直接在 headers (json) 里加一个 `Cookie` 头即可,
多个 cookie 用 `;` 分隔, 例如:
{
"Cookie": "session_id=abc123; user_token=xyz789"
}
"""
import json
import logging
from typing import Any, Optional
import gradio as gr
import httpx
logger = logging.getLogger("http_proxy")
SUPPORTED_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
MAX_BODY_PREVIEW = 2 * 1024 * 1024 # 2 MB
def _parse_json_field(value: Optional[str], field_name: str) -> Optional[Any]:
if value is None:
return None
text = value.strip()
if not text:
return None
try:
return json.loads(text)
except json.JSONDecodeError as e:
raise ValueError(f"`{field_name}` JSON 解析失败: {e}") from e
def http_proxy_request(
url: str,
method: str = "GET",
headers: Optional[str] = None,
params: Optional[str] = None,
body: Optional[str] = None,
timeout: float = 30.0,
follow_redirects: bool = True,
verify_ssl: bool = True,
):
"""转发 HTTP 请求并返回 (status_code, response_headers_json, response_body)."""
if not url or not url.strip():
return 0, "{}", "url 不能为空"
method_upper = (method or "GET").upper()
if method_upper not in SUPPORTED_METHODS:
return 0, "{}", f"不支持的 method: {method}"
try:
headers_dict = _parse_json_field(headers, "headers")
params_dict = _parse_json_field(params, "params")
except ValueError as e:
return 0, "{}", str(e)
request_kwargs: dict = {
"method": method_upper,
"url": url.strip(),
"headers": headers_dict,
"params": params_dict,
"timeout": float(timeout) if timeout else 30.0,
}
if body and body.strip() and method_upper not in ("GET", "HEAD"):
body_text = body
try:
request_kwargs["json"] = json.loads(body_text)
except (json.JSONDecodeError, TypeError):
request_kwargs["content"] = body_text.encode("utf-8")
logger.info(f"proxy request: {method_upper} {url}")
try:
with httpx.Client(
follow_redirects=bool(follow_redirects),
verify=bool(verify_ssl),
http2=True,
) as client:
response = client.request(**request_kwargs)
except httpx.HTTPError as e:
logger.exception("http error")
return 0, "{}", f"http 请求异常: {e!r}"
except Exception as e:
logger.exception("unexpected error")
return 0, "{}", f"未知异常: {e!r}"
response_headers = json.dumps(
dict(response.headers), ensure_ascii=False, indent=2,
)
content_bytes = response.content or b""
if len(content_bytes) > MAX_BODY_PREVIEW:
truncated_note = (
f"\n\n... [body 已截断, 原始长度 {len(content_bytes)} bytes, "
f"仅展示前 {MAX_BODY_PREVIEW} bytes]"
)
content_bytes = content_bytes[:MAX_BODY_PREVIEW]
else:
truncated_note = ""
try:
encoding = response.encoding or "utf-8"
response_body = content_bytes.decode(encoding, errors="replace")
except Exception:
response_body = repr(content_bytes)
return response.status_code, response_headers, response_body + truncated_note
def get_http_proxy_tab():
method_choices = SUPPORTED_METHODS
with gr.TabItem("http_proxy"):
gr.Markdown(
"## HTTP 代理\n"
"在页面上手动调试请求,也可以从外部通过 Gradio API 调用,"
"把本服务当成统一的 HTTP 代理网关。\n\n"
"- gradio_client: `client.predict(..., api_name=\"/http_proxy\")`\n"
"- HTTP: `POST /gradio_api/call/http_proxy`,参数顺序见输入框"
)
with gr.Row():
with gr.Column(scale=3):
http_url = gr.Textbox(
label="url",
placeholder="https://example.com/api/xxx",
lines=1,
)
http_method = gr.Dropdown(
label="method",
choices=method_choices,
value=method_choices[0],
)
http_headers = gr.Textbox(
label="headers (json)",
value="{}",
lines=4,
max_lines=20,
)
http_params = gr.Textbox(
label="query params (json)",
value="{}",
lines=4,
max_lines=20,
)
http_body = gr.Textbox(
label="body (raw text 或 json)",
value="",
lines=6,
max_lines=30,
)
with gr.Row():
http_timeout = gr.Number(label="timeout", value=30.0)
http_follow_redirects = gr.Checkbox(
label="follow_redirects", value=True,
)
http_verify_ssl = gr.Checkbox(
label="verify_ssl", value=True,
)
http_send = gr.Button("send", variant="primary")
with gr.Column(scale=4):
http_status_code = gr.Number(label="status_code", interactive=False)
http_response_headers = gr.Textbox(
label="response headers",
lines=8,
max_lines=40,
interactive=False,
show_copy_button=True,
)
http_response_body = gr.Textbox(
label="response body",
lines=20,
max_lines=200,
interactive=False,
show_copy_button=True,
)
inputs = [
http_url,
http_method,
http_headers,
http_params,
http_body,
http_timeout,
http_follow_redirects,
http_verify_ssl,
]
outputs = [
http_status_code,
http_response_headers,
http_response_body,
]
http_send.click(
fn=http_proxy_request,
inputs=inputs,
outputs=outputs,
api_name="http_proxy",
)
gr.Examples(
examples=[
[
"https://httpbin.org/get",
"GET",
"{}",
"{\"q\": \"hello\"}",
"",
30.0,
True,
True,
],
[
"https://httpbin.org/post",
"POST",
"{\"Content-Type\": \"application/json\"}",
"{}",
"{\"hello\": \"world\"}",
30.0,
True,
True,
],
[
"https://api.github.com/repos/gradio-app/gradio",
"GET",
"{\"User-Agent\": \"gradio-http-proxy\"}",
"{}",
"",
30.0,
True,
True,
],
[
"https://httpbin.org/cookies",
"GET",
"{\"Cookie\": \"session_id=abc123; user_token=xyz789\"}",
"{}",
"",
30.0,
True,
True,
],
],
inputs=inputs,
)
return locals()
if __name__ == "__main__":
with gr.Blocks() as block:
_ = get_http_proxy_tab()
block.queue().launch()