import t, c from stdint import * import string import socket import memhub import stdio import stdlib import zlib.pyzlib as pyzlib # ============================================================ # requests/__init__.vp — HTTP(S) 客户端库包入口(Python requests 风格) # # 包结构: # requests HTTP 客户端(纯 socket 实现,本文件) # requests.https HTTPS 客户端(openssl SSL/TLS + 证书核验,见 https.vp) # # 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 GZIP_BUFSIZ: t.CDefine = 65536 # 模块内重定向 / 失败诊断(静态缓冲) _http_err_store: t.CArray[t.CChar, 512] def last_error() -> str: """返回 HTTP 层最近一次失败的原因(无则空串)。""" return _http_err_store def _SetHttpError(text: str): if text is None: return _http_err_store[0] = 0 stdio.snprintf(_http_err_store, 512, "%s", text) # ============================================================ # 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 body_len: INT # Response body length _raw_buf: str # 原始响应缓冲(含头与正文,release 释放) _body_owned: str # 独立解码正文缓冲(chunked 等解码结果,release 一并释放) # ============================================================ # 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 resp._body_owned = 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 # ============================================================ # 公共辅助(供 requests.https 等子模块复用) # ============================================================ def create_response() -> Response | t.CPtr: """Create and initialize an empty HTTP Response (allocated from _mbuddy). Call release(resp) to free it after use. """ return _AllocResponse() def parse_status_line(resp_text: str, out_code: INT | t.CPtr) -> INT: """Parse HTTP status line, write status code into out_code.""" return _ParseStatusLine(resp_text, out_code) def find_body_start(resp_text: str, resp_len: INT) -> INT: """Locate the start of the HTTP response body.""" return _FindBodyStart(resp_text, resp_len) # ============================================================ # 响应解析(header 查询 / chunked 解码 / 统一 parse_response) # ============================================================ def _CIEq(a: t.CChar, b: t.CChar) -> bool: """大小写不敏感字符相等(仅 ASCII)。""" if a >= 'A' and a <= 'Z': a = t.CChar(INT(a) - INT('A') + INT('a')) if b >= 'A' and b <= 'Z': b = t.CChar(INT(b) - INT('A') + INT('a')) return a == b def header_value(resp: Response | t.CPtr, name: str, out: str, out_size: INT) -> INT: """在原始响应中按名查询响应头(大小写不敏感,返回首值)。 Args: resp: Response(其 _raw_buf 保留原始响应) name: 头名称,如 "Content-Type" out / out_size: 值写入缓冲 Returns: 0 找到并拷贝;-1 未找到或参数非法。 """ if resp is None or resp._raw_buf is None or name is None or out is None: return -1 p: str = resp._raw_buf # 跳过状态行 while p[0] != '\0' and p[0] != '\n': p += 1 if p[0] == '\n': p += 1 # 逐头行匹配 while p[0] != '\0' and p[0] != '\r' and p[0] != '\n': h: str = p q: str = name matched: bool = True while q[0] != '\0': if h[0] != q[0] and not _CIEq(h[0], q[0]): matched = False break h += 1 q += 1 # name 匹配后允许 [空格]* ':'(RFC 无空格,宽松兼容) while h[0] == ' ': h += 1 if matched and h[0] == ':': h += 1 while h[0] == ' ' or h[0] == '\t': h += 1 i: INT = 0 while h[0] != '\0' and h[0] != '\r' and h[0] != '\n' and i < out_size - 1: out[i] = h[0] i += 1 h += 1 out[i] = 0 return 0 # 跳到下一行 while p[0] != '\0' and p[0] != '\n': p += 1 if p[0] == '\n': p += 1 return -1 def _HexVal(ch: t.CChar) -> INT: if ch >= '0' and ch <= '9': return INT(ch - '0') if ch >= 'a' and ch <= 'f': return INT(ch - 'a') + 10 if ch >= 'A' and ch <= 'F': return INT(ch - 'A') + 10 return -1 def decode_chunked(src: str, src_len: INT, dst: str, dst_cap: INT) -> INT: """按 RFC 7230 解码 chunked 正文(不处理 chunk-ext,trailer 忽略)。 Args: src / src_len: chunked 编码数据 dst / dst_cap: 解码输出缓冲 Returns: 解码字节数;失败返回 -1。 """ if src is None or dst is None: return -1 r: INT = 0 w: INT = 0 while r < src_len: # 1) chunk size(hex,可带 ;ext 扩展) size: INT = 0 digit_seen: bool = False while r < src_len: ch: t.CChar = src[r] if ch == '\r' or ch == '\n' or ch == ';': break hv: INT = _HexVal(ch) if hv < 0: return -1 digit_seen = True if size > 0x7fffffff: return -1 size = size * 16 + hv r += 1 if not digit_seen: return -1 # 2) 跳过扩展与行尾 while r < src_len and src[r] != '\n': r += 1 if r < src_len and src[r] == '\n': r += 1 # 3) 结束块 if size == 0: return w # 4) 拷贝数据 + 跳过 CRLF if w + size > dst_cap: return -1 k: INT = 0 while k < size: dst[w + k] = src[r + k] k += 1 w += size r += size if r < src_len and src[r] == '\r': r += 1 if r < src_len and src[r] == '\n': r += 1 return w def parse_response(resp: Response | t.CPtr, total: INT) -> INT: """统一解析已接收的原始响应并填充 Response 字段。 - 解析状态码 / 定位正文 - Transfer-Encoding: chunked 时解码到独立缓冲(_body_owned), 原始缓冲 _raw_buf 保留(供 header_value 查询) Returns: 0 成功;-1 失败(此时 text=None / body_len=0)。 """ if resp is None or resp._raw_buf is None: return -1 buf: str = resp._raw_buf buf[total] = 0 _ParseStatusLine(buf, c.Addr(resp.status_code)) body_offset: INT = _FindBodyStart(buf, total) if body_offset < 0 or body_offset >= total: resp.text = None resp.body_len = 0 return 0 # 检测 Transfer-Encoding: chunked te_buf: t.CArray[t.CChar, 64] chunked: bool = False if header_value(resp, "Transfer-Encoding", te_buf, 64) == 0: if string.strstr(te_buf, "chunked") is not None: chunked = True if chunked: new_buf: str = _mbuddy.alloc(HTTP_BUFSIZ) if new_buf is None: resp.text = None resp.body_len = 0 return -1 dec_len: INT = decode_chunked(buf + body_offset, total - body_offset, new_buf, HTTP_BUFSIZ) if dec_len < 0: _mbuddy.free(new_buf) resp.text = None resp.body_len = 0 return -1 new_buf[dec_len] = 0 resp._body_owned = new_buf resp.text = new_buf resp.body_len = dec_len else: resp.text = buf + body_offset resp.body_len = total - body_offset # ---- Content-Encoding: gzip 解压(zlib.pyzlib,纯 Viper 实现)---- ce_buf: t.CArray[t.CChar, 64] if header_value(resp, "Content-Encoding", ce_buf, 64) == 0 \ and string.strstr(ce_buf, "gzip") is not None \ and resp.text is not None and resp.body_len > 0: src_len2: t.CSizeT = t.CSizeT(resp.body_len) olen2: t.CSizeT = 0 gz_out: BYTEPTR = pyzlib.decompress(_mbuddy, BYTEPTR(resp.text), src_len2, 15, GZIP_BUFSIZ, c.Addr(olen2)) if gz_out is not None: old_owned: str = resp._body_owned if old_owned is not None: _mbuddy.free(old_owned) gz_str: str = t.CChar(t.CUInt64T(gz_out), t.CPtr) gz_str[olen2] = 0 resp._body_owned = gz_str resp.text = gz_str resp.body_len = INT(olen2) else: # 解压失败:保留原始 body,记录诊断信息 stdio.snprintf(_http_err_store, 512, "gzip decode failed: %s", pyzlib.get_error()) return 0 # ============================================================ # 公共 API # ============================================================ def _HttpGetOnce(url: str) -> Response | t.CPtr: """HTTP GET 单次请求(不跟随重定向)。请求头带 Accept-Encoding: gzip。""" 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\nAccept-Encoding: gzip\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_response(resp, total) return resp def _HttpPostOnce(url: str, data: str = None, content_type: str = "application/x-www-form-urlencoded") -> Response | t.CPtr: """HTTP POST 单次请求(不跟随重定向)。请求头带 Accept-Encoding: gzip。""" 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\nAccept-Encoding: gzip\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 parse_response(resp, total) return resp # ============================================================ # 重定向跟随 # ============================================================ def _ResolveRedirectUrl(base: str, loc: str, out: str, cap: INT) -> INT: """把 Location 合并为完整 URL(支持绝对 / 协议相对 / 根相对),结果写 out。 Returns: 0 成功;-1 失败。 """ if base is None or loc is None or out is None: return -1 lb: INT = INT(string.strlen(loc)) if string.strncmp(loc, "http://", 7) == 0 or string.strncmp(loc, "https://", 8) == 0: if lb >= cap: return -1 string.strcpy(out, loc) return 0 # 取 base 的 scheme://authority 前缀 q: str = base while q[0] != '\0' and q[0] != ':': q += 1 if q[0] != ':': return -1 q += 1 if q[0] != '/': return -1 q += 1 if q[0] != '/': return -1 q += 1 while q[0] != '\0' and q[0] != '/': q += 1 prefix_len: INT = INT(t.CUInt64T(q) - t.CUInt64T(base)) need: INT = prefix_len + lb + 2 if need >= cap: return -1 i: INT = 0 while i < prefix_len: out[i] = base[i] i += 1 if loc[0] == '/': j: INT = 0 while loc[j] != '\0': out[prefix_len + j] = loc[j] j += 1 out[prefix_len + j] = 0 else: out[prefix_len] = '/' j: INT = 0 while loc[j] != '\0': out[prefix_len + 1 + j] = loc[j] j += 1 out[prefix_len + 1 + j] = 0 return 0 def _HttpRedirectLoop(method: str, url: str, data: str, content_type: str) -> Response | t.CPtr: """GET/POST + 重定向跟随(http:// 链,最多 5 跳)。""" if _mbuddy is None: return None cur: str = _mbuddy.alloc(2048) if cur is None: return None if INT(string.strlen(url)) >= 2047: _SetHttpError("url too long") _mbuddy.free(cur) return None string.strcpy(cur, url) hop: INT = 0 while hop < 5: resp: Response | t.CPtr = None if string.strcmp(method, "POST") == 0: resp = _HttpPostOnce(cur, data, content_type) else: resp = _HttpGetOnce(cur) if resp is None: _mbuddy.free(cur) return None code: INT = resp.status_code if not _IsHttpRedirect(code): _mbuddy.free(cur) return resp loc: str = _mbuddy.alloc(2048) if loc is None: _mbuddy.free(cur) return resp if header_value(resp, "Location", loc, 2048) != 0: _mbuddy.free(loc) _mbuddy.free(cur) return resp nxt: str = _mbuddy.alloc(2048) if nxt is None: _mbuddy.free(loc) _mbuddy.free(cur) return resp if _ResolveRedirectUrl(cur, loc, nxt, 2048) != 0: _SetHttpError("unresolvable redirect Location") release(resp) _mbuddy.free(nxt) _mbuddy.free(loc) _mbuddy.free(cur) return None if string.strncmp(nxt, "https://", 8) == 0: _SetHttpError("redirect to https:// needs requests.https") release(resp) _mbuddy.free(nxt) _mbuddy.free(loc) _mbuddy.free(cur) return None release(resp) _mbuddy.free(loc) _mbuddy.free(cur) cur = nxt hop += 1 _SetHttpError("too many redirects") _mbuddy.free(cur) return None def _IsHttpRedirect(code: INT) -> bool: return code >= 300 and code < 400 def get(url: str) -> Response | t.CPtr: """HTTP GET(自动跟随 3xx 重定向,最多 5 跳)。 Args: url: Full URL (e.g., "http://example.com/path") Returns: Pointer to a Response object, None on failure (see last_error()). """ return _HttpRedirectLoop("GET", url, None, None) def post(url: str, data: str = None, content_type: str = "application/x-www-form-urlencoded") -> Response | t.CPtr: """HTTP POST(自动跟随 3xx 重定向,最多 5 跳)。""" return _HttpRedirectLoop("POST", url, data, content_type) 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._body_owned is not None: _mbuddy.free(resp._body_owned) if resp._raw_buf is not None: _mbuddy.free(resp._raw_buf) _mbuddy.free(t.CVoid(t.CUInt64T(resp), t.CPtr)) # ============================================================ # 大文件 Range 下载原语(requests.download 使用) # # _DlOnce 单次发起 "Range: bytes=start-end" 的 GET(不带 Accept-Encoding, # 保证响应是原始字节),把响应体写入调用方提供的 dst 缓冲。 # 线程安全:本函数不使用 _mbuddy(响应/请求缓冲一律 stdlib), # socket 连接独立,供多线程并行切片下载并发调用。 # # 返回: >=0 响应体字节数;-1 参数错;-2 3xx 重定向(Location 写 out_loc, # 由 download 模块跟随);-3 其它失败(last_error() 查看原因)。 # out_status/out_total 分别输出 HTTP 状态码与 Content-Range 总大小。 # ============================================================ def _RawHeaderValue(buf: str, total: INT, name: str, out: str, out_size: INT) -> INT: """对原始响应头区做大小写不敏感查询(0=命中并拷贝值;-1=未命中)。""" if buf is None or name is None or out is None or out_size <= 0: return -1 body: INT = _FindBodyStart(buf, total) if body < 0: body = total nl: INT = 0 while nl < body: eol: INT = nl while eol < body and buf[eol] != '\r' and buf[eol] != '\n': eol += 1 if eol > nl: k: INT = 0 ok: bool = True while name[k] != 0 and nl + k < eol: if not _CIEq(buf[nl + k], name[k]): ok = False break k += 1 if ok and name[k] == 0 and nl + k < eol and buf[nl + k] == ':': v: INT = nl + k + 1 while v < eol and (buf[v] == ' ' or buf[v] == '\t'): v += 1 vlen: INT = eol - v if vlen > out_size - 1: vlen = out_size - 1 for i in range(vlen): out[i] = buf[v + i] out[vlen] = 0 return 0 nl = eol while nl < body and (buf[nl] == '\r' or buf[nl] == '\n'): nl += 1 return -1 def _StatusRaw(buf: str) -> INT: """从 "HTTP/1.1 206 ..." 首行解析状态码。""" if buf is None: return 0 i: INT = 0 while buf[i] == ' ': i += 1 while buf[i] != 0 and buf[i] != ' ': i += 1 while buf[i] == ' ': i += 1 code: INT = 0 while buf[i] >= '0' and buf[i] <= '9': code = code * 10 + INT(buf[i] - '0') i += 1 return code def _CLenOf(buf: str, total: INT) -> INT: """读取 Content-Length(无则 -1)。""" hbuf: t.CArray[t.CChar, 32] if _RawHeaderValue(buf, total, "Content-Length", hbuf, 32) != 0: return -1 v: INT = 0 i: INT = 0 while hbuf[i] >= '0' and hbuf[i] <= '9': v = v * 10 + INT(hbuf[i] - '0') i += 1 return v def _CRTotalOf(buf: str, total: INT) -> t.CSizeT: """从 Content-Range: bytes s-e/TOTAL 解析 TOTAL(非 206/缺头则 0)。""" hbuf: t.CArray[t.CChar, 128] if _RawHeaderValue(buf, total, "Content-Range", hbuf, 128) != 0: return 0 p: INT = 0 while hbuf[p] != 0 and hbuf[p] != '/': p += 1 if hbuf[p] == 0: return 0 p += 1 v: t.CSizeT = 0 while hbuf[p] != 0 and hbuf[p] >= '0' and hbuf[p] <= '9': v = v * 10 + t.CSizeT(hbuf[p] - '0') p += 1 return v def _DlOnce(url: str, start: t.CSizeT, count: INT, dst: str, dst_cap: INT, out_status: INT | t.CPtr, out_loc: str, out_loccap: INT, out_total: t.CSizeT | t.CPtr) -> INT: """HTTP 单次 Range 拉取(线程安全,见模块注释)。""" 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 if _EnsureSocketInit() != 0: _SetHttpError("socket init failed") return -3 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: _SetHttpError("bad http url") return -3 sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM) if sock.fd == socket.INVALID_SOCKET: _SetHttpError("socket() failed") return -3 if sock.connect(host_buf, port) != 0: _SetHttpError("connect failed") sock.close() return -3 req: str = stdlib.malloc(1024) if req is None: sock.close() _SetHttpError("out of memory") 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 = sock.send(req, INT(string.strlen(req))) stdlib.free(req) if sent <= 0: sock.close() _SetHttpError("send failed") 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 = sock.recv(dst + total, room) if n <= 0: break total += n if tgt < 0: bo: INT = _FindBodyStart(dst, total) if bo >= 0: cl: INT = _CLenOf(dst, total) if cl >= 0: tgt = bo + cl if tgt >= 0 and total >= tgt: break sock.close() dst[total] = 0 st: INT = _StatusRaw(dst) c.Set(c.Deref(out_status), st) if st == 0: _SetHttpError("empty response") return -3 if _IsHttpRedirect(st): if out_loc is not None and out_loccap > 0: if _RawHeaderValue(dst, total, "Location", out_loc, out_loccap) != 0: out_loc[0] = 0 return -2 if st != 200 and st != 206: stdio.snprintf(_http_err_store, 512, "http status %d", st) return -3 if st == 206: c.Set(c.Deref(out_total), _CRTotalOf(dst, total)) else: cl2: INT = _CLenOf(dst, total) if cl2 > 0: c.Set(c.Deref(out_total), t.CSizeT(cl2)) bo: INT = _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