Files
Viper_VRTE/requests/__init__.vp
T

899 lines
27 KiB
Plaintext
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
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