Spaces:
Sleeping
Sleeping
| #!/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() | |