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))