Files
Viper_VRTE/requests/https.vp
T
2026-09-18 20:01:14 +08:00

1093 lines
38 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import string
import socket
import stdio
import stdlib
import requests
import openssl
import json
import memhub
# ============================================================
# requests/https.vp — HTTPS 客户端(openssl SSL/TLS + 证书核验)
#
# 与 requests(HTTP)同名 API,但走 TLS 并默认校验对端证书:
# import requests.https as rhttps
# resp = rhttps.get("https://example.com/", verify=True)
# if resp is not None:
# print(resp.status_code)
# print(resp.text)
# requests.release(resp)
#
# 说明:
# - verify=True 时校验证书链 + 主机名(hostname 校验 SSL_set1_host),
# 提供 ca_file 指向 PEM CA bundle;不提供则走系统默认路径
# (Windows 上通常为空,真实站点会因"无法验证证书链"失败返回 None)。
# - 失败时返回 None,可用 last_error() 获取原因文本。
# - 握手成功后可用 last_tls_info() 查看 TLS 版本 / 加密套件 / 对端证书主题。
# ============================================================
HTTPS_PORT: t.CDefine = 443
# 模块内部状态
_ossl_ready: bool = False
_socket_ready: bool = False
_err_store: t.CArray[t.CChar, 512] # 最近一次失败原因(静态缓冲)
_info_store: t.CArray[t.CChar, 512] # 最近一次成功握手的 TLS 元信息
_scratch_store: t.CArray[t.CChar, 512] # 临时拼接缓冲
_last_verify_val: INT = -1 # 最近一次握手后 SSL_get_verify_result(0=通过;-1=未启用)
_last_verify_flag: INT = -1 # 最近一次请求 verify 标志(1=开启;0=关闭;-1=未知)
# ============================================================
# SSE 流式内容接收区(post_stream 专用;单路请求,UI 线程轮询拉取)
# _st_len: 工作线程已写入的内容字节数
# _st_consumed: UI 已消费游标(仅拉取方推进)
# _st_state: 0=运行中 1=完成(2xx) 2=失败
# _st_status: HTTP 状态码(失败网络路径为负错误码)
# ============================================================
STREAM_CAP: t.CDefine = 262144
_st_buf: t.CArray[t.CChar, 262144]
_st_len: t.CSizeT = 0
_st_consumed: t.CSizeT = 0
_st_state: INT = 0
_st_status: INT = 0
_st_err: t.CArray[t.CChar, 512]
def stream_begin():
"""开始新一轮流式接收:重置缓冲/游标/状态。"""
global _st_len, _st_consumed, _st_state, _st_status
_st_len = 0
_st_consumed = 0
_st_state = 0
_st_status = 0
_st_err[0] = 0
def stream_pull(out: str, cap: INT) -> INT:
"""拉取未消费的增量内容到 out(自动补 0),返回拷贝字节数。"""
global _st_consumed
if out is None or cap <= 1:
return 0
avail: INT = 0
if _st_len > _st_consumed:
avail = INT(_st_len - _st_consumed)
if avail > cap - 1:
avail = cap - 1
i: t.CSizeT = 0
while i < t.CSizeT(avail):
out[i] = _st_buf[_st_consumed + i]
i += 1
out[t.CSizeT(avail)] = 0
_st_consumed += t.CSizeT(avail)
return avail
def stream_state() -> INT:
"""0=运行中 1=完成(2xx) 2=失败。"""
return _st_state
def stream_status() -> INT:
"""HTTP 状态码;负数为网络/解析错误。"""
return _st_status
def stream_error_text() -> str:
"""失败原因文本(_st_state==2 时)。"""
return _st_err
def last_verify_result() -> int:
"""返回最近一次握手的证书校验结果:0 通过;>0 失败码;-1 未开启校验。"""
return _last_verify_val
def last_verify_enabled() -> int:
"""返回最近一次请求是否开启校验(1=是;0=否;-1=未知)。"""
return _last_verify_flag
# ============================================================
# 内部辅助
# ============================================================
def last_error() -> str:
"""返回最近一次失败的原因文本(无则空串)。"""
return _err_store
def last_tls_info() -> str:
"""返回最近一次成功握手的 TLS 信息(版本/套件/对端主题),无则空串。"""
return _info_store
def _RecordErrorText(text: str):
"""把失败原因写入静态缓冲 _err_store。"""
if text is None: return
_err_store[0] = 0
stdio.snprintf(_err_store, 512, "%s", text)
def _RecordInfoText(text: str):
"""把 TLS 元信息写入静态缓冲 _info_store。"""
if text is None: return
_info_store[0] = 0
stdio.snprintf(_info_store, 512, "%s", text)
def _EnsureReady() -> INT:
"""初始化 socket 与 openssl(均幂等)。"""
global _socket_ready, _ossl_ready
if not _socket_ready:
if socket.SocketInit() != 0:
_RecordErrorText("socket init failed")
return -1
_socket_ready = True
if not _ossl_ready:
if openssl.OPENSSL_init_ssl(0, None) != 1:
_RecordErrorText("OPENSSL_init_ssl failed")
return -1
_ossl_ready = True
return 0
def _ParseHttpsUrl(url: str, host_buf: str, host_size: INT, path_buf: str, path_size: INT, port: INT | t.CPtr) -> INT:
"""解析 https://host[:port][/path],端口缺省 443。"""
if url is None: return -1
if host_buf is None or path_buf is None or port is None: return -1
p: str = url
# Skip "https://"(8 字节)
if string.strncmp(p, "https://", 8) == 0:
p += 8
# Parse host
host_start: str = p
host_len: INT = 0
while p[0] != '\0' and p[0] != ':' and p[0] != '/':
host_len += 1
p += 1
if host_len == 0 or host_len >= host_size: return -1
string.strncpy(host_buf, host_start, host_len)
host_buf[host_len] = 0
# Parse port
if p[0] == ':':
p += 1
port_val: INT = 0
while p[0] >= '0' and p[0] <= '9':
port_val = port_val * 10 + INT(p[0] - '0')
p += 1
c.DerefAs(port, port_val)
else:
c.DerefAs(port, HTTPS_PORT)
# Parse path
if p[0] == '/':
path_start: str = p
path_len: INT = 0
while p[0] != '\0':
path_len += 1
p += 1
if path_len >= path_size: return -1
string.strncpy(path_buf, path_start, path_len)
path_buf[path_len] = 0
else:
if path_size < 2: return -1
path_buf[0] = '/'
path_buf[1] = 0
return 0
def _ScratchBuf() -> str:
"""返回静态拼接缓冲(512B,复用)。"""
return _scratch_store
def _TlsErrText():
"""把最近一次 openssl 错误队列文本写入静态 scratch 缓冲。"""
e: t.CUInt32T = openssl.ERR_get_error()
if e == 0:
_scratch_store[0] = 0
string.strncpy(_scratch_store, "openssl error (no detail)", 26)
return
openssl.ERR_error_string_n(e, _scratch_store, 512)
# ============================================================
# 核心请求
# ============================================================
def _HttpsRequest(method: str, url: str, verify: bool, ca_file: str, data: str, content_type: str, auth: str = None) -> rawptr[requests.Response]:
"""HTTPS GET/POST 核心实现。
verify=False 时跳过证书链/主机名校验(不推荐);verify=True 时
ca_file 提供 PEM CA bundle,否则使用系统默认 CA 路径。
auth(可选):形如 "Bearer <key>" 的 Authorization 头值,
非空时仅在 POST 请求头中插入 Authorization 头(默认 None = 行为不变)。
"""
global _last_verify_val, _last_verify_flag
if requests._mbuddy is None: return None
_last_verify_flag = 1 if verify else 0
resp: rawptr[requests.Response] = requests.create_response()
if resp is None: return None
if _EnsureReady() != 0:
requests.release(resp)
return None
# 临时缓冲(错误文本格式化;openssl 详情走模块级 scratch)
errmsg: str = requests._mbuddy.alloc(512)
if errmsg is None:
requests.release(resp)
return None
# ---- 解析 URL ----
host_buf: t.CArray[t.CChar, 256]
path_buf: t.CArray[t.CChar, 1024]
port: INT = 0
if _ParseHttpsUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
stdio.snprintf(errmsg, 512, "bad https url: %s", url)
_RecordErrorText(errmsg)
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- 建立 TCP 连接 ----
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
_RecordErrorText("socket() failed")
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
if sock.connect(host_buf, port) != 0:
stdio.snprintf(errmsg, 512, "connect %s:%d failed", host_buf, port)
_RecordErrorText(errmsg)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- SSL_CTX / SSL ----
ctx: openssl.SslCtxPtr = openssl.SSL_CTX_new(openssl.TLS_client_method())
if ctx is None:
_RecordErrorText("SSL_CTX_new failed")
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
if verify:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyPeer, None)
if ca_file is not None and ca_file[0] != 0:
if openssl.SSL_CTX_load_verify_locations(ctx, ca_file, None) != 1:
_TlsErrText()
stdio.snprintf(errmsg, 512, "load CA file failed: %s (%s)", ca_file, _ScratchBuf())
_RecordErrorText(errmsg)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
else:
# 系统默认路径(Windows 上通常为空,证书链校验将失败)
openssl.SSL_CTX_set_default_verify_paths(ctx)
else:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyNone, None)
ssl: openssl.SslPtr = openssl.SSL_new(ctx)
if ssl is None:
_RecordErrorText("SSL_new failed")
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
openssl.SSL_set_fd(ssl, sock.fd)
# SNI(SSL_set_tlsext_host_name 是宏 → 直接 SSL_ctrl)
openssl.SSL_ctrl(ssl, openssl.SslCtrlSetTlsextHostname, openssl.TlsextNametypeHostName, host_buf)
# 主机名校验(TLS 内核对证书 SAN/CN 与 host 比对)
if verify:
openssl.SSL_set1_host(ssl, host_buf)
# ---- 握手 ----
hret: INT = openssl.SSL_connect(ssl)
if hret <= 0:
want: INT = openssl.SSL_get_error(ssl, hret)
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_connect failed (want=%d): %s", want, _ScratchBuf())
_RecordErrorText(errmsg)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- 记录最近一次证书校验结果(供 last_verify_result 查询)----
if verify:
_last_verify_val = INT(openssl.SSL_get_verify_result(ssl))
else:
_last_verify_val = -1
# ---- 证书链校验结果复核 ----
if verify:
vres: t.CInt32T = openssl.SSL_get_verify_result(ssl)
if vres != 0:
reason: str = openssl.X509_verify_cert_error_string(vres)
if reason is None:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed: error %d", INT(vres))
else:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed (%d): %s", INT(vres), reason)
_RecordErrorText(errmsg)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
# ---- 记录 TLS 元信息(版本 / 套件 / 对端证书主题)----
name_buf: str = requests._mbuddy.alloc(512)
if name_buf is not None:
peer: openssl.X509Ptr = openssl.SSL_get1_peer_certificate(ssl)
if peer is not None:
subj: openssl.X509NamePtr = openssl.X509_get_subject_name(peer)
if subj is not None:
name_buf[0] = 0
openssl.X509_NAME_oneline(subj, name_buf, 512)
name_buf[511] = 0
openssl.X509_free(peer)
else:
name_buf[0] = 0
string.strncpy(name_buf, "(no peer certificate)", 21)
name_buf[21] = 0
ver: str = openssl.SSL_get_version(ssl)
cipher: str = openssl.SSL_CIPHER_get_name(openssl.SSL_get_current_cipher(ssl))
stdio.snprintf(errmsg, 512, "%s | cipher %s | peer: %s",
ver if ver is not None else "?", cipher if cipher is not None else "?", name_buf)
_RecordInfoText(errmsg)
requests._mbuddy.free(name_buf)
# ---- 组请求头并发送 ----
req_buf: str = requests._mbuddy.alloc(requests.HTTP_BUFSIZ)
if req_buf is None:
_RecordErrorText("alloc request buffer failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
body_len: INT = 0
is_post: bool = string.strcmp(method, "POST") == 0
if is_post:
body_len = INT(string.strlen(data)) if data is not None else 0
ct: str = content_type if content_type is not None else "application/x-www-form-urlencoded"
# 分段组头,便于按需插入 Authorization(auth 非空才加,默认路径输出与原来完全一致)
hdr_off: INT = 0
hdr_cap: INT = requests.HTTP_BUFSIZ
stdio.snprintf(req_buf, hdr_cap, "POST %s HTTP/1.1\r\nHost: %s\r\n", path_buf, host_buf)
hdr_off = INT(string.strlen(req_buf))
if auth is not None and auth[0] != 0:
stdio.snprintf(req_buf + hdr_off, hdr_cap - hdr_off, "Authorization: %s\r\n", auth)
hdr_off = INT(string.strlen(req_buf))
stdio.snprintf(req_buf + hdr_off, hdr_cap - hdr_off,
"Content-Type: %s\r\nContent-Length: %d\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n",
ct, body_len)
else:
stdio.snprintf(req_buf, requests.HTTP_BUFSIZ,
"GET %s HTTP/1.1\r\nHost: %s\r\nAccept-Encoding: gzip\r\nConnection: close\r\n\r\n",
path_buf, host_buf)
hdr_len: INT = INT(string.strlen(req_buf))
sent: INT = openssl.SSL_write(ssl, req_buf, hdr_len)
if sent <= 0:
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_write header failed: %s", _ScratchBuf())
_RecordErrorText(errmsg)
requests._mbuddy.free(req_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
if is_post and body_len > 0 and data is not None:
openssl.SSL_write(ssl, data, body_len)
requests._mbuddy.free(req_buf)
# ---- 接收响应(缓冲自动翻倍增长, 支持 MB 级正文, 如 MNIST .gz 下载) ----
resp_cap: INT = requests.HTTP_BUFSIZ
resp_buf: str = requests._mbuddy.alloc(t.CSizeT(resp_cap))
if resp_buf is None:
_RecordErrorText("alloc response buffer failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
total: INT = 0
while True:
room: INT = resp_cap - 1 - total
if room <= 0:
# 缓冲已满仍可能有数据: 翻倍扩容(硬上限 256MB, 防失控流)
if resp_cap >= 268435456:
break
resp_cap = resp_cap * 2
nb: t.CPtr = requests._mbuddy.realloc(resp_buf, t.CSizeT(resp_cap))
if nb is None:
_RecordErrorText("realloc response buffer failed")
requests._mbuddy.free(resp_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
resp_buf = t.CChar(t.CUInt64T(nb), t.CPtr)
room = resp_cap - 1 - total
n: INT = openssl.SSL_read(ssl, resp_buf + total, room)
if n <= 0:
break
total += n
resp_buf[total] = 0
# 关闭 TLS 与 TCP
openssl.SSL_shutdown(ssl)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
resp._raw_buf = resp_buf
requests.parse_response(resp, total)
requests._mbuddy.free(errmsg)
return resp
# ============================================================
# 公共 API
# ============================================================
def _HttpsRedirectLoop(method: str, url: str, verify: bool, ca_file: str, data: str, content_type: str, auth: str = None) -> rawptr[requests.Response]:
"""HTTPS GET/POST + 重定向跟随(跨 http/https,最多 5 跳)。
auth(可选)原样透传给 _HttpsRequest;None 时行为与原来完全一致。
注意:跨主机重定向时 Authorization 同样会透传(仅提示,逻辑未改动)。
"""
if requests._mbuddy is None: return None
cur: str = requests._mbuddy.alloc(2048)
if cur is None: return None
if INT(string.strlen(url)) >= 2047:
_RecordErrorText("url too long")
requests._mbuddy.free(cur)
return None
string.strcpy(cur, url)
hop: INT = 0
while hop < 5:
resp: rawptr[requests.Response] = _HttpsRequest(method, cur, verify, ca_file, data, content_type, auth)
if resp is None:
requests._mbuddy.free(cur)
return None
code: INT = resp.status_code
if not (code >= 300 and code < 400):
requests._mbuddy.free(cur)
return resp
loc: str = requests._mbuddy.alloc(2048)
if loc is None:
requests._mbuddy.free(cur)
return resp
if requests.header_value(resp, "Location", loc, 2048) != 0:
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
return resp
nxt: str = requests._mbuddy.alloc(2048)
if nxt is None:
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
return resp
if requests._ResolveRedirectUrl(cur, loc, nxt, 2048) != 0:
_RecordErrorText("unresolvable redirect Location")
requests.release(resp)
requests._mbuddy.free(nxt)
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
return None
requests.release(resp)
# 跨协议分流:https → 继续本循环;http → 交给 requests(http)链
if string.strncmp(nxt, "https://", 8) != 0:
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
http_resp: rawptr[requests.Response] = requests.get(nxt)
requests._mbuddy.free(nxt)
return http_resp
requests._mbuddy.free(loc)
requests._mbuddy.free(cur)
cur = nxt
hop += 1
_RecordErrorText("too many redirects")
requests._mbuddy.free(cur)
return None
def get(url: str, verify: bool = True, ca_file: str = None) -> rawptr[requests.Response]:
"""HTTPS GET request(自动跟随 3xx 重定向,最多 5 跳)。
Args:
url: Full URL (e.g., "https://example.com/path")
verify: True 时校验证书链 + 主机名(默认)
ca_file: PEM CA bundle 路径;None 时使用系统默认 CA 路径
Returns:
Pointer to a Response object, None on failure (see last_error()).
"""
return _HttpsRedirectLoop("GET", url, verify, ca_file, None, None)
def post(url: str, data: str = None, content_type: str = "application/x-www-form-urlencoded",
verify: bool = True, ca_file: str = None, auth: str = None) -> rawptr[requests.Response]:
"""HTTPS POST request(自动跟随 3xx 重定向,最多 5 跳)。
Args:
url: Full URL
data: Request body data
content_type: Content-Type header
verify: True 时校验证书链 + 主机名(默认)
ca_file: PEM CA bundle 路径;None 时使用系统默认 CA 路径
auth: 可选 Authorization 头值(形如 "Bearer <key>"),
非空时随请求头发送;None/空串 = 与旧版完全一致。
Returns:
Pointer to a Response object, None on failure (see last_error()).
"""
return _HttpsRedirectLoop("POST", url, verify, ca_file, data, content_type, auth)
# ============================================================
# SSE 流式 Chat Completions(post_stream)
#
# 与 post() 不同:请求头带 Accept: text/event-stream;响应体按行解析
# SSE(data: {json}),把每段 choices[0].delta.content 抽进 _st_buf 供
# UI 线程 stream_pull() 增量拉取显示。全程阻塞读取,应在后台线程调用。
#
# 返回: >=0 HTTP 状态码(2xx 正常完成;3xx/4xx/5xx 状态仍返回给调用方,
# 正文中的非 SSE 内容不会进 _st_buf);-1/-2 网络/解析失败
# (_st_state=2,失败原因见 stream_error_text()/last_error())。
# ============================================================
def _SseDeltaContent(root: rawptr[json.JsonValue]) -> str:
"""从流式 data 事件取 choices[0].delta.content;缺失返回 None。"""
if root is None or not root.is_object():
return None
cv: rawptr[json.JsonValue] = root["choices"]
if cv is None or not cv.is_array():
return None
m0: rawptr[json.JsonValue] = cv.get_item(0)
if m0 is None or not m0.is_object():
return None
dv: rawptr[json.JsonValue] = m0["delta"]
if dv is None or not dv.is_object():
return None
content_v: rawptr[json.JsonValue] = dv["content"]
if content_v is None or not content_v.is_string():
return None
return content_v.as_string()
def _StreamAppendContent(txt: str):
"""把一段增量内容追加到 _st_buf(超容量则截断丢弃并记录)。"""
global _st_len
if txt is None:
return
pos: t.CSizeT = _st_len
i: t.CSizeT = 0
while txt[i] != 0:
if pos >= t.CSizeT(STREAM_CAP) - 1:
break
_st_buf[pos] = txt[i]
pos += 1
i += 1
_st_len = pos
def _SetStreamFail(rc: INT):
"""流式失败收尾:从 _err_store 拷原因文本并置状态。"""
global _st_state, _st_status
_st_state = 2
_st_status = rc
stdio.snprintf(_st_err, 512, "%s", _err_store)
def post_stream(url: str, data: str, content_type: str = "application/json",
verify: bool = True, ca_file: str = None, auth: str = None) -> INT:
"""SSE 流式 POST(阻塞,应在后台线程调用)。
data 应为已含 "stream":true 的 JSON 请求体。
返回 HTTP 状态码;负数为网络/解析失败(_st_state=2)。
"""
global _st_state, _st_status
if requests._mbuddy is None:
return -1
_st_state = 0
_st_status = 0
# Winsock/openssl 初始化(幂等;流式首次可能在后台线程执行)
if _EnsureReady() != 0:
_SetStreamFail(-1)
return -1
# ---- 连接/TLS(与 _HttpsRequest 一致)----
errmsg: str = requests._mbuddy.alloc(512)
if errmsg is None:
return -1
host_buf: t.CArray[t.CChar, 256]
path_buf: t.CArray[t.CChar, 1024]
port: INT = 0
if _ParseHttpsUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
stdio.snprintf(errmsg, 512, "bad https url: %s", url)
_RecordErrorText(errmsg)
_SetStreamFail(-1)
requests._mbuddy.free(errmsg)
return -1
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
_RecordErrorText("socket() failed")
_SetStreamFail(-1)
requests._mbuddy.free(errmsg)
return -1
if sock.connect(host_buf, port) != 0:
stdio.snprintf(errmsg, 512, "connect %s:%d failed", host_buf, port)
_RecordErrorText(errmsg)
_SetStreamFail(-1)
sock.close()
requests._mbuddy.free(errmsg)
return -1
ctx: openssl.SslCtxPtr = openssl.SSL_CTX_new(openssl.TLS_client_method())
if ctx is None:
_RecordErrorText("SSL_CTX_new failed")
_SetStreamFail(-1)
sock.close()
requests._mbuddy.free(errmsg)
return -1
if verify:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyPeer, None)
if ca_file is not None and ca_file[0] != 0:
if openssl.SSL_CTX_load_verify_locations(ctx, ca_file, None) != 1:
_TlsErrText()
stdio.snprintf(errmsg, 512, "load CA file failed: %s (%s)", ca_file, _ScratchBuf())
_RecordErrorText(errmsg)
_SetStreamFail(-1)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
else:
openssl.SSL_CTX_set_default_verify_paths(ctx)
else:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyNone, None)
ssl: openssl.SslPtr = openssl.SSL_new(ctx)
if ssl is None:
_RecordErrorText("SSL_new failed")
_SetStreamFail(-1)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
openssl.SSL_set_fd(ssl, sock.fd)
openssl.SSL_ctrl(ssl, openssl.SslCtrlSetTlsextHostname, openssl.TlsextNametypeHostName, host_buf)
if verify:
openssl.SSL_set1_host(ssl, host_buf)
hret: INT = openssl.SSL_connect(ssl)
if hret <= 0:
want: INT = openssl.SSL_get_error(ssl, hret)
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_connect failed (want=%d): %s", want, _ScratchBuf())
_RecordErrorText(errmsg)
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
if verify:
vres: t.CInt32T = openssl.SSL_get_verify_result(ssl)
if vres != 0:
reason: str = openssl.X509_verify_cert_error_string(vres)
if reason is None:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed: error %d", INT(vres))
else:
stdio.snprintf(errmsg, 512, "SSL certificate verify failed (%d): %s", INT(vres), reason)
_RecordErrorText(errmsg)
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
# ---- 组请求头(POST + Accept: text/event-stream)并发送 ----
req_buf: str = requests._mbuddy.alloc(requests.HTTP_BUFSIZ)
if req_buf is None:
_RecordErrorText("alloc request buffer failed")
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
body_len: INT = INT(string.strlen(data)) if data is not None else 0
ct: str = content_type if content_type is not None else "application/json"
hdr_off: INT = 0
stdio.snprintf(req_buf, requests.HTTP_BUFSIZ, "POST %s HTTP/1.1\r\nHost: %s\r\n", path_buf, host_buf)
hdr_off = INT(string.strlen(req_buf))
if auth is not None and auth[0] != 0:
stdio.snprintf(req_buf + hdr_off, requests.HTTP_BUFSIZ - hdr_off, "Authorization: %s\r\n", auth)
hdr_off = INT(string.strlen(req_buf))
stdio.snprintf(req_buf + hdr_off, requests.HTTP_BUFSIZ - hdr_off,
"Content-Type: %s\r\nContent-Length: %d\r\nAccept: text/event-stream\r\nConnection: close\r\n\r\n",
ct, body_len)
hdr_len: INT = INT(string.strlen(req_buf))
sent: INT = openssl.SSL_write(ssl, req_buf, hdr_len)
if sent <= 0:
_TlsErrText()
stdio.snprintf(errmsg, 512, "SSL_write header failed: %s", _ScratchBuf())
_RecordErrorText(errmsg)
_SetStreamFail(-1)
requests._mbuddy.free(req_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
if body_len > 0 and data is not None:
openssl.SSL_write(ssl, data, body_len)
requests._mbuddy.free(req_buf)
# ---- 读取响应:跳过响应头 → SSE 行解析 → 抽 delta.content ----
rd: str = requests._mbuddy.alloc(16384)
if rd is None:
_RecordErrorText("alloc stream buffer failed")
_SetStreamFail(-1)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
# JSON 解析池(事件粒度 reset,足够容纳单个 data 事件)
pmem: str = stdlib.malloc(524288)
if pmem is None:
_RecordErrorText("alloc json pool failed")
_SetStreamFail(-1)
requests._mbuddy.free(rd)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
return -1
jpool: rawptr[memhub.MemPool] = memhub.MemPool(pmem, 524288)
leftover: t.CArray[t.CChar, 16384]
llen: INT = 0
header_done: INT = 0
status_code: INT = 0
had_read: INT = 0
while True:
n: INT = openssl.SSL_read(ssl, rd, 16384)
if n <= 0:
break
had_read = 1
# 1) 追加到 leftover
ai: INT = 0
while ai < n and llen < 16383:
leftover[llen] = rd[ai]
llen += 1
ai += 1
# 2) 逐行处理
while True:
# 找行尾 \n
nl: INT = -1
k: INT = 0
while k < llen:
if leftover[k] == 10:
nl = k
break
k += 1
if nl < 0:
break
e: INT = nl
if e > 0 and leftover[e - 1] == 13:
e -= 1
leftover[e] = 0
line: str = leftover
if header_done == 0:
if string.strncmp(line, "HTTP/", 5) == 0:
# 状态码:HTTP/1.1 200 OK
sp: INT = 0
while line[sp] != 0 and line[sp] != ' ':
sp += 1
if line[sp] == ' ':
sp += 1
code_val: INT = 0
while line[sp] >= '0' and line[sp] <= '9':
code_val = code_val * 10 + INT(line[sp] - '0')
sp += 1
status_code = code_val
if e == 0:
header_done = 1
else:
if e > 0 and line[0] != ':':
if string.strncmp(line, "data:", 5) == 0:
pl: str = line + 5
while pl[0] == ' ':
pl += 1
if pl[0] != 0:
if string.strncmp(pl, "[DONE]", 6) != 0 or pl[6] != 0:
jpool.reset()
ev: rawptr[json.JsonValue] = json.parse(jpool, pl)
dtx: str = _SseDeltaContent(ev)
if dtx is not None:
_StreamAppendContent(dtx)
# 3) 移除已处理行
rem: INT = llen - (nl + 1)
j2: INT = 0
while j2 < rem:
leftover[j2] = leftover[nl + 1 + j2]
j2 += 1
llen = rem
# ---- 收尾 ----
openssl.SSL_shutdown(ssl)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(rd)
stdlib.free(pmem)
requests._mbuddy.free(errmsg)
if status_code == 0:
if had_read == 0:
_RecordErrorText("no response data")
else:
_RecordErrorText("missing HTTP status line")
_SetStreamFail(-2)
return -2
_st_status = status_code
if status_code >= 200 and status_code < 300:
_st_state = 1
else:
_st_state = 2
stdio.snprintf(_st_err, 512, "HTTP %d(请检查 Key/模型/地址或账号额度)", status_code)
return status_code
# ============================================================
# 大文件 Range 下载原语(requests.download 使用,与 requests._DlOnce 镜像)
#
# 线程安全:本函数不使用 _mbuddy(请求/响应缓冲一律 stdlib),每个连接独立
# SSL_CTX/SSL/socket,供多线程并行切片并发调用(OPENSSL_init_ssl 由主线程
# 先 _EnsureReady 预热一次)。
#
# 返回: >=0 响应体字节数;-1 参数错;-2 3xx 重定向(Location 写 out_loc);
# -3 其它失败(last_error() 查看原因)。
# ============================================================
def _DlOnce(url: str, start: t.CSizeT, count: INT, verify: bool, ca_file: str,
dst: str, dst_cap: INT, out_status: INT | t.CPtr,
out_loc: str, out_loccap: INT,
out_total: t.CSizeT | t.CPtr) -> INT:
"""HTTPS 单次 Range 拉取(线程安全,见模块注释)。"""
global _last_verify_val, _last_verify_flag
if url is None or dst is None or dst_cap <= 0 \
or out_status is None or out_total is None:
return -1
c.Set(c.Deref(out_status), 0)
c.Set(c.Deref(out_total), 0)
if out_loc is not None and out_loccap > 0:
out_loc[0] = 0
_last_verify_flag = 1 if verify else 0
if _EnsureReady() != 0:
return -3
host_buf: t.CArray[t.CChar, 256]
path_buf: t.CArray[t.CChar, 1024]
port: INT = 0
if _ParseHttpsUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
_RecordErrorText("bad https url")
return -3
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
_RecordErrorText("socket() failed")
return -3
if sock.connect(host_buf, port) != 0:
_RecordErrorText("connect failed")
sock.close()
return -3
ctx: openssl.SslCtxPtr = openssl.SSL_CTX_new(openssl.TLS_client_method())
if ctx is None:
_RecordErrorText("SSL_CTX_new failed")
sock.close()
return -3
if verify:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyPeer, None)
if ca_file is not None and ca_file[0] != 0:
if openssl.SSL_CTX_load_verify_locations(ctx, ca_file, None) != 1:
_RecordErrorText("load CA file failed")
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
else:
openssl.SSL_CTX_set_default_verify_paths(ctx)
else:
openssl.SSL_CTX_set_verify(ctx, openssl.SslVerifyNone, None)
ssl: openssl.SslPtr = openssl.SSL_new(ctx)
if ssl is None:
_RecordErrorText("SSL_new failed")
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
openssl.SSL_set_fd(ssl, sock.fd)
openssl.SSL_ctrl(ssl, openssl.SslCtrlSetTlsextHostname, openssl.TlsextNametypeHostName, host_buf)
if verify:
openssl.SSL_set1_host(ssl, host_buf)
hret: INT = openssl.SSL_connect(ssl)
if hret <= 0:
_RecordErrorText("SSL_connect failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
if verify:
_last_verify_val = INT(openssl.SSL_get_verify_result(ssl))
if _last_verify_val != 0:
_RecordErrorText("SSL certificate verify failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
else:
_last_verify_val = -1
# 组 Range 请求头(不带 Accept-Encoding,保证响应为原始字节)
req: str = stdlib.malloc(1024)
if req is None:
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
cnt: INT = count
if cnt < 0:
cnt = 0
end: t.CSizeT = start + t.CSizeT(cnt) - 1
if cnt <= 0:
end = start
stdio.snprintf(req, 1024,
"GET %s HTTP/1.1\r\nHost: %s\r\nRange: bytes=%zu-%zu\r\nConnection: close\r\n\r\n",
path_buf, host_buf, start, end)
sent: INT = openssl.SSL_write(ssl, req, INT(string.strlen(req)))
stdlib.free(req)
if sent <= 0:
_RecordErrorText("SSL_write failed")
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
return -3
total: INT = 0
tgt: INT = -1
while total < dst_cap - 1:
room: INT = dst_cap - 1 - total
if room > 4096:
room = 4096
n: INT = openssl.SSL_read(ssl, dst + total, room)
if n <= 0:
break
total += n
if tgt < 0:
bo: INT = requests._FindBodyStart(dst, total)
if bo >= 0:
cl: INT = requests._CLenOf(dst, total)
if cl >= 0:
tgt = bo + cl
if tgt >= 0 and total >= tgt:
break
openssl.SSL_shutdown(ssl)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
dst[total] = 0
st: INT = requests._StatusRaw(dst)
c.Set(c.Deref(out_status), st)
if st == 0:
_RecordErrorText("empty response")
return -3
if requests._IsHttpRedirect(st):
if out_loc is not None and out_loccap > 0:
if requests._RawHeaderValue(dst, total, "Location", out_loc, out_loccap) != 0:
out_loc[0] = 0
return -2
if st != 200 and st != 206:
_RecordErrorText("http status not 200/206")
return -3
if st == 206:
c.Set(c.Deref(out_total), requests._CRTotalOf(dst, total))
else:
cl2: INT = requests._CLenOf(dst, total)
if cl2 > 0:
c.Set(c.Deref(out_total), t.CSizeT(cl2))
bo: INT = requests._FindBodyStart(dst, total)
if bo < 0 or bo >= total:
return 0
blen: INT = total - bo
if blen > cnt and cnt > 0:
blen = cnt
return blen