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 " 的 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 "), 非空时随请求头发送;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