import t, c from stdint import * import string import socket import memhub import stdio # ============================================================ # requests.py — HTTP client library (Python original style) # # Usage: # 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) # ============================================================ # Module-level global MBuddy pointer (assigned by the user in main) _mbuddy: memhub.MemBuddy | t.CPtr # Socket initialization flag _socket_ready: bool = False HTTP_PORT: t.CDefine = 80 HTTP_BUFSIZ: t.CDefine = 4096 # ============================================================ # Response — HTTP response object # ============================================================ class Response: """HTTP response object, similar to how Python's requests.Response works. Allocated from _mbuddy, call release(resp) to free it after use. """ status_code: INT # HTTP status code (e.g., 200, 404) text: str # Response body text (points into _raw_buf) body_len: INT # Response body length _raw_buf: str # Raw buffer pointer # ============================================================ # Internal auxiliary functions # ============================================================ def _EnsureSocketInit() -> INT: """Ensure Socket library is initialized (idempotent).""" 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: """Allocate Response struct from _mbuddy and initialize it in-place.""" 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: """Parse URL and extract host, port, and 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 # Skip "http://" if string.strncmp(p, "http://", 7) == 0: p += 7 # 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, HTTP_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 _ParseStatusLine(resp_text: str, out_code: INT | t.CPtr) -> INT: """Parse HTTP status line and extract status code.""" if resp_text is None or out_code is None: return -1 p: str = resp_text # Skip "HTTP/x.x " while p[0] != '\0' and p[0] != ' ': p += 1 if p[0] == ' ': p += 1 # Parse status code 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) # Find \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: """Find the start of the HTTP response body (after rnrn).""" 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 request. Args: url: Full URL (e.g., "http://example.com/path") Returns: Pointer to a Response object (allocated from _mbuddy), returns None on failure. """ if _mbuddy is None: return None resp: Response | t.CPtr = _AllocResponse() if resp is None: return None if _EnsureSocketInit() != 0: release(resp) return None # Parse 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 # Create 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 # Build HTTP request 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 # Receive response body 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 # Parse status line _ParseStatusLine(resp_buf, c.Addr(resp.status_code)) # Find body start 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 request. Args: url: Full URL data: Request body data content_type: Content-Type header (default is "application/x-www-form-urlencoded") Returns: Pointer to Response object (allocated from _mbuddy), returns None on failure. """ if _mbuddy is None: return None resp: Response | t.CPtr = _AllocResponse() if resp is None: return None if _EnsureSocketInit() != 0: release(resp) return None # Parse 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" # Create 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 # Build HTTP request 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)) # Send headers + 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) # Receive response body 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): """Release Response object and its internal buffers. Args: resp: The Response object to release """ 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))