Files
TransPyC/includes/requests.py
2026-07-18 19:25:40 +08:00

361 lines
9.6 KiB
Python
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 memhub
import stdio
# ============================================================
# requests.py — HTTP 客户端库Python 原版风格)
#
# 用法:
# requests._mbuddy = my_mbuddy
# resp = requests.get("http://example.com/path")
# if resp is not None:
# print(resp.status_code)
# print(resp.text)
# requests.release(resp)
# ============================================================
# 模块级全局 MBuddy 指针(由用户在 main 中赋值)
_mbuddy: memhub.MemBuddy | t.CPtr
# Socket 初始化标记
_socket_ready: bool = False
HTTP_PORT: t.CDefine = 80
HTTP_BUFSIZ: t.CDefine = 4096
# ============================================================
# Response — HTTP 响应对象
# ============================================================
class Response:
"""HTTP 响应对象,贴近 Python requests.Response 用法。
从 _mbuddy 分配,使用完毕后调用 release(resp) 释放。
"""
status_code: INT # HTTP 状态码(如 200、404
text: str # 响应体文本(指向 _raw_buf 内部)
body_len: INT # 响应体长度
_raw_buf: str # 原始缓冲区指针
# ============================================================
# 内部辅助函数
# ============================================================
def _EnsureSocketInit() -> INT:
"""确保 Socket 库已初始化(幂等)。"""
global _socket_ready
if _socket_ready: return 0
result: INT = socket.SocketInit()
if result == 0:
_socket_ready = True
return result
def _AllocResponse() -> Response | t.CPtr:
"""从 _mbuddy 分配 Response 结构体并就地初始化。"""
if _mbuddy is None: return None
resp: Response | t.CPtr = _mbuddy.alloc(Response.__sizeof__())
if resp is None: return None
resp.status_code = 0
resp.text = None
resp.body_len = 0
resp._raw_buf = None
return resp
def _ParseUrl(url: str, host_buf: str, host_size: INT, path_buf: str, path_size: INT, port: INT | t.CPtr) -> INT:
"""解析 URL提取 host、port、path。"""
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
# 跳过 "http://"
if string.strncmp(p, "http://", 7) == 0:
p += 7
# 解析 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
# 解析 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, HTTP_PORT)
# 解析 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 _ParseStatusLine(resp_text: str, out_code: INT | t.CPtr) -> INT:
"""解析 HTTP 状态行,提取状态码。返回状态行结束偏移。"""
if resp_text is None or out_code is None: return -1
p: str = resp_text
# 跳过 "HTTP/x.x "
while p[0] != '\0' and p[0] != ' ':
p += 1
if p[0] == ' ': p += 1
# 解析状态码
code: INT = 0
while p[0] >= '0' and p[0] <= '9':
code = code * 10 + INT(p[0] - '0')
p += 1
c.DerefAs(out_code, code)
# 找到 \n
while p[0] != '\0' and p[0] != '\n':
p += 1
if p[0] == '\n': p += 1
return INT(t.CUInt64T(p) - t.CUInt64T(resp_text))
def _FindBodyStart(resp_text: str, resp_len: INT) -> INT:
"""找到 HTTP 响应体起始位置(\\r\\n\\r\\n 之后)。"""
if resp_text is None: return -1
end_of_headers: str = string.strstr(resp_text, "\r\n\r\n")
if end_of_headers is None:
end_of_headers = string.strstr(resp_text, "\n\n")
if end_of_headers is None:
return -1
return INT(t.CUInt64T(end_of_headers) - t.CUInt64T(resp_text)) + 2
return INT(t.CUInt64T(end_of_headers) - t.CUInt64T(resp_text)) + 4
# ============================================================
# 公共 API
# ============================================================
def get(url: str) -> Response | t.CPtr:
"""HTTP GET 请求。
Args:
url: 完整 URL (如 "http://example.com/path")
Returns:
Response 对象指针(从 _mbuddy 分配),失败返回 None。
使用完毕后调用 release(resp) 释放。
"""
if _mbuddy is None: return None
resp: Response | t.CPtr = _AllocResponse()
if resp is None: return None
if _EnsureSocketInit() != 0:
release(resp)
return None
# 解析 URL
host_buf: t.CArray[t.CChar, 256]
path_buf: t.CArray[t.CChar, 1024]
port: INT = 0
if _ParseUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
release(resp)
return None
# 创建并连接 Socket
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
release(resp)
return None
if sock.connect(host_buf, port) != 0:
sock.close()
release(resp)
return None
# 构建 HTTP 请求
req_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
if req_buf is None:
sock.close()
release(resp)
return None
stdio.snprintf(req_buf, HTTP_BUFSIZ,
"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
path_buf, host_buf)
req_len: INT = INT(string.strlen(req_buf))
sent: INT = sock.send(req_buf, req_len)
_mbuddy.free(req_buf)
if sent <= 0:
sock.close()
release(resp)
return None
# 接收响应
resp_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
if resp_buf is None:
sock.close()
release(resp)
return None
total: INT = 0
while total < HTTP_BUFSIZ - 1:
n: INT = sock.recv(resp_buf + total, HTTP_BUFSIZ - 1 - total)
if n <= 0: break
total += n
resp_buf[total] = 0
sock.close()
resp._raw_buf = resp_buf
# 解析状态码
_ParseStatusLine(resp_buf, c.Addr(resp.status_code))
# 找到 body
body_offset: INT = _FindBodyStart(resp_buf, total)
if body_offset < 0 or body_offset >= total:
resp.text = None
resp.body_len = 0
else:
resp.text = resp_buf + body_offset
resp.body_len = total - body_offset
return resp
def post(url: str, data: str = None, content_type: str = "application/x-www-form-urlencoded") -> Response | t.CPtr:
"""HTTP POST 请求。
Args:
url: 完整 URL
data: 请求体数据
content_type: Content-Type 头(默认 "application/x-www-form-urlencoded"
Returns:
Response 对象指针(从 _mbuddy 分配),失败返回 None。
"""
if _mbuddy is None: return None
resp: Response | t.CPtr = _AllocResponse()
if resp is None: return None
if _EnsureSocketInit() != 0:
release(resp)
return None
# 解析 URL
host_buf: t.CArray[t.CChar, 256]
path_buf: t.CArray[t.CChar, 1024]
port: INT = 0
if _ParseUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
release(resp)
return None
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/x-www-form-urlencoded"
# 创建并连接 Socket
sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
if sock.fd == socket.INVALID_SOCKET:
release(resp)
return None
if sock.connect(host_buf, port) != 0:
sock.close()
release(resp)
return None
# 构建 HTTP 请求
req_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
if req_buf is None:
sock.close()
release(resp)
return None
stdio.snprintf(req_buf, HTTP_BUFSIZ,
"POST %s HTTP/1.1\r\nHost: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n",
path_buf, host_buf, ct, body_len)
hdr_len: INT = INT(string.strlen(req_buf))
# 发送头部 + body
sent: INT = sock.send(req_buf, hdr_len)
if sent > 0 and body_len > 0 and data is not None:
sock.send(data, body_len)
_mbuddy.free(req_buf)
# 接收响应
resp_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
if resp_buf is None:
sock.close()
release(resp)
return None
total: INT = 0
while total < HTTP_BUFSIZ - 1:
n: INT = sock.recv(resp_buf + total, HTTP_BUFSIZ - 1 - total)
if n <= 0: break
total += n
resp_buf[total] = 0
sock.close()
resp._raw_buf = resp_buf
_ParseStatusLine(resp_buf, c.Addr(resp.status_code))
body_offset: INT = _FindBodyStart(resp_buf, total)
if body_offset < 0 or body_offset >= total:
resp.text = None
resp.body_len = 0
else:
resp.text = resp_buf + body_offset
resp.body_len = total - body_offset
return resp
def release(resp: Response | t.CPtr):
"""释放 Response 对象及其内部缓冲区。
Args:
resp: 要释放的 Response 对象
"""
if resp is None: return
if _mbuddy is None: return
if resp._raw_buf is not None:
_mbuddy.free(resp._raw_buf)
_mbuddy.free(t.CVoid(t.CUInt64T(resp), t.CPtr))