fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
264 lines
7.4 KiB
Python
264 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from dataclasses import dataclass, field
|
|
|
|
from curl_cffi.requests import Session
|
|
|
|
from .parse import SUPPORTED_METHODS, process_url
|
|
from .request import _execute_request
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# .http file support
|
|
# see: https://github.com/JetBrains/http-request-in-editor-spec/blob/master/spec.md
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass
|
|
class HttpFileRequest:
|
|
"""A single request parsed from an .http file."""
|
|
|
|
method: str = "GET"
|
|
url: str = ""
|
|
headers: dict[str, str] = field(default_factory=dict)
|
|
body: str = ""
|
|
|
|
|
|
def parse_http_file(text: str) -> list[HttpFileRequest]:
|
|
"""Parse an .http file into a list of requests.
|
|
|
|
Supports the HTTP Request in Editor spec:
|
|
- Requests separated by ###
|
|
"""
|
|
blocks = []
|
|
current_lines: list[str] = []
|
|
for line in text.splitlines():
|
|
if line.strip().startswith("###"):
|
|
if current_lines:
|
|
blocks.append(current_lines)
|
|
current_lines = []
|
|
else:
|
|
current_lines.append(line)
|
|
if current_lines:
|
|
blocks.append(current_lines)
|
|
|
|
requests = []
|
|
for block_lines in blocks:
|
|
req = _parse_http_block(block_lines)
|
|
if req is not None:
|
|
requests.append(req)
|
|
return requests
|
|
|
|
|
|
def _parse_http_block(lines: list[str]) -> HttpFileRequest | None:
|
|
"""Parse a single request block from an .http file.
|
|
|
|
Each block contains:
|
|
- Request line: [METHOD] URL [HTTP/version]
|
|
- Headers: Name: Value
|
|
- Body after blank line
|
|
- Comments with # or //
|
|
- File references: < filepath
|
|
"""
|
|
# States: "request" -> "headers" -> "body"
|
|
state = "request"
|
|
method = "GET"
|
|
url = ""
|
|
headers: dict[str, str] = {}
|
|
body_lines: list[str] = []
|
|
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
|
|
# skip any comments
|
|
if stripped.startswith("#") or stripped.startswith("//"):
|
|
continue
|
|
|
|
match state:
|
|
case "request":
|
|
if not stripped:
|
|
continue
|
|
parts = stripped.split()
|
|
if (
|
|
parts[0].upper() in SUPPORTED_METHODS
|
|
or parts[0].upper() == "CONNECT"
|
|
):
|
|
method = parts[0].upper()
|
|
url = parts[1] if len(parts) > 1 else ""
|
|
else:
|
|
url = parts[0] # GET implied
|
|
state = "headers"
|
|
|
|
case "headers":
|
|
if not stripped:
|
|
state = "body" # blank line means body
|
|
elif ":" in stripped:
|
|
name, _, value = stripped.partition(":")
|
|
headers[name.strip()] = value.strip()
|
|
else:
|
|
state = "body"
|
|
body_lines.append(line)
|
|
|
|
case "body":
|
|
if stripped.startswith("> ") or stripped.startswith("<> "):
|
|
# TODO: implement body checking
|
|
break
|
|
if stripped.startswith("< "):
|
|
_, filepath = stripped.split()
|
|
filepath = filepath.strip()
|
|
try:
|
|
with open(filepath) as f:
|
|
body_lines.append(f.read())
|
|
except FileNotFoundError:
|
|
print(
|
|
f"Error: referenced file not found: {filepath}",
|
|
file=sys.stderr,
|
|
)
|
|
else:
|
|
body_lines.append(line)
|
|
|
|
if not url:
|
|
return None
|
|
|
|
return HttpFileRequest(
|
|
method=method,
|
|
url=url,
|
|
headers=headers,
|
|
body="\n".join(body_lines).strip(),
|
|
)
|
|
|
|
|
|
def _run_http_file(
|
|
args: argparse.Namespace, text: str, session: Session | None = None
|
|
) -> int:
|
|
"""Execute requests parsed from .http file content."""
|
|
|
|
requests = parse_http_file(text)
|
|
if not requests:
|
|
print("Error: no requests found in file.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
failed = 0
|
|
for i, req in enumerate(requests, 1):
|
|
print(f"--- [{i}] {req.method} {req.url}", file=sys.stderr)
|
|
|
|
url = process_url(req.url)
|
|
|
|
rc = _execute_request(
|
|
args,
|
|
req.method,
|
|
url,
|
|
headers=req.headers,
|
|
data=req.body or None,
|
|
session=session,
|
|
)
|
|
if rc != 0:
|
|
failed += 1
|
|
|
|
return failed
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HAR file support
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_SKIP_HAR_HEADERS = frozenset(
|
|
{
|
|
"content-length",
|
|
"transfer-encoding",
|
|
":method",
|
|
":path",
|
|
":scheme",
|
|
":authority",
|
|
}
|
|
)
|
|
|
|
|
|
def _run_har_file(
|
|
args: argparse.Namespace, text: str, session: Session | None = None
|
|
) -> int:
|
|
"""Execute requests parsed from HAR file content."""
|
|
try:
|
|
har = json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
print(f"Error: invalid HAR JSON: {exc}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
entries = har.get("log", {}).get("entries", [])
|
|
if not entries:
|
|
print("Error: no entries found in HAR file.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
failed = 0
|
|
for i, entry in enumerate(entries, 1):
|
|
req = entry.get("request", {})
|
|
method = req.get("method", "GET").upper()
|
|
url = req.get("url", "")
|
|
|
|
if not url:
|
|
print(f"--- [{i}] skipping entry with no URL", file=sys.stderr)
|
|
continue
|
|
|
|
print(f"--- [{i}] {method} {url}", file=sys.stderr)
|
|
|
|
headers = {}
|
|
for h in req.get("headers", []):
|
|
name = h.get("name", "")
|
|
if name.lower() not in _SKIP_HAR_HEADERS:
|
|
headers[name] = h.get("value", "")
|
|
headers = headers or None
|
|
|
|
post_data = req.get("postData")
|
|
data = post_data.get("text", "") if post_data else None
|
|
|
|
rc = _execute_request(
|
|
args,
|
|
method,
|
|
url,
|
|
headers=headers,
|
|
data=data,
|
|
session=session,
|
|
)
|
|
if rc != 0:
|
|
failed += 1
|
|
|
|
return failed
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Entrypoint
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def handle_run(args: argparse.Namespace) -> None:
|
|
"""Execute batch requests."""
|
|
filepath = args.file
|
|
try:
|
|
with open(filepath) as f:
|
|
text = f.read()
|
|
except FileNotFoundError:
|
|
print(f"Error: file not found: {filepath}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
use_session = getattr(args, "session", True)
|
|
session = Session(impersonate=args.impersonate) if use_session else None
|
|
|
|
try:
|
|
if filepath.endswith(".har"):
|
|
failed = _run_har_file(args, text, session=session)
|
|
elif filepath.endswith(".http"):
|
|
failed = _run_http_file(args, text, session=session)
|
|
else:
|
|
print(f"Error: unsupported file format: {filepath}", file=sys.stderr)
|
|
sys.exit(1)
|
|
finally:
|
|
if session:
|
|
session.close()
|
|
|
|
if failed > 0:
|
|
print(f"\n{failed} request(s) failed.", file=sys.stderr)
|
|
sys.exit(1)
|