概述
nim-requests 是 Python 著名 HTTP 库 requests 的 Nim 2 完整复刻,
提供完全一致的 API 风格,同时采用 Nim 原生风格实现。
支持 GET / POST / PUT / DELETE / HEAD / OPTIONS / PATCH 全部 HTTP 方法,
内置 Session 会话、Cookie 持久化、Basic / Bearer 认证、文件上传、流式下载等核心功能。
全部 80+ 测试通过 — 覆盖所有 HTTP 方法、Session、Cookie、认证、异常、工具函数和集成测试。
安装
环境要求
| 依赖 | 版本 | 说明 |
|---|---|---|
| Nim | ≥ 2.0.0 | 推荐 2.2.10 |
| OpenSSL | — | 需安装 libssl-dev(HTTPS 支持) |
使用 nimble
nimble install nim-requests
本地使用
# 将 src/ 目录放入项目,直接 import
import requests
# 或通过 nimble 安装为本地包
nimble develop
编译选项
# 编译时启用 SSL
nim c -d:ssl your_app.nim
# nimble 测试
nimble test
快速开始
GET 请求
import requests
let r = get("https://httpbin.org/json")
echo r.code # 200
echo r.body # 响应体
echo r.json["slideshow"].getStr # JSON 解析
echo r.headers["content-type"] # 响应头
POST 请求
import requests, std/json
# 表单数据
let r1 = post("https://httpbin.org/post", data = "key=value")
# JSON 数据
let r2 = post("https://httpbin.org/post",
jsonBody = %*{"name": "nim", "version": 2})
Session 会话
let s = session(timeout = 10)
s.headers["Authorization"] = "Bearer my-token"
let r = s.reqGet("https://httpbin.org/headers")
echo r.json
s.reqClose()
带参数请求
import std/tables
var params = {"page": "1", "size": "20"}.toTable
let r = get("https://httpbin.org/get", params = params)
顶层 API
与 Python requests 完全一致的便捷函数,每个调用创建独立客户端。
| 函数 | 签名 | 说明 |
|---|---|---|
get() PASS |
get(url, params, headers, timeout, allowRedirects, verify, proxies) |
发送 GET 请求 |
post() PASS |
post(url, data, jsonBody, files, headers, timeout, allowRedirects, verify, proxies) |
发送 POST 请求,支持表单/JSON/文件上传 |
put() PASS |
put(url, data, jsonBody, headers, timeout, allowRedirects, verify, proxies) |
发送 PUT 请求 |
delete() PASS |
delete(url, headers, timeout, allowRedirects, verify, proxies) |
发送 DELETE 请求 |
head() PASS |
head(url, headers, timeout, allowRedirects, verify, proxies) |
发送 HEAD 请求 |
options() PASS |
options(url, headers, timeout, allowRedirects, verify, proxies) |
发送 OPTIONS 请求 |
patch() PASS |
patch(url, data, jsonBody, headers, timeout, allowRedirects, verify, proxies) |
发送 PATCH 请求 |
request() PASS |
request(method, url, data, jsonBody, headers, params, ...) |
通用请求方法,通过 RequestMethod 枚举指定方法 |
session() |
session(headers, cookies, auth, verify, timeout, proxies, cert, maxRedirects) |
创建 Session 对象 |
downloadFile() |
downloadFile(url, destPath, headers, timeout, verify, proxies) |
下载文件到本地路径 |
提示:所有顶层函数均返回
HttpResp 对象,支持 r.ok、r.text()、r.json()、r.raiseForStatus() 等便捷方法。
request() 通用方法
import requests
# 使用枚举指定方法
let r1 = request(rmGet, "https://httpbin.org/get")
let r2 = request(rmPost, "https://httpbin.org/post", data = "a=1")
let r3 = request(rmDelete, "https://httpbin.org/delete")
# 带参数
var params = {"q": "nim"}.toTable
let r4 = request(rmGet, "https://httpbin.org/get", params = params)
Session 会话
Session 维持跨请求的持久状态:Headers、Cookies、认证信息、代理设置等。
与 Python requests.Session 行为完全一致。
创建 Session
import requests
let s = session(
headers = {"User-Agent": "MyApp/1.0"}.toTable,
auth = ("username", "password"),
timeout = 15,
verify = true,
maxRedirects = 5,
)
Session 方法
| 方法 | 说明 | 测试 |
|---|---|---|
s.reqGet(url, params, hdrs) | GET 请求 | PASS |
s.reqPost(url, data, jsonBody, hdrs) | POST 请求 | PASS |
s.reqPut(url, data, jsonBody, hdrs) | PUT 请求 | PASS |
s.reqDelete(url, hdrs) | DELETE 请求 | PASS |
s.reqHead(url, hdrs) | HEAD 请求 | PASS |
s.reqPatch(url, data, jsonBody, hdrs) | PATCH 请求 | PASS |
s.reqOptions(url, hdrs) | OPTIONS 请求 | PASS |
s.reqClose() | 关闭会话 | PASS |
Cookie 持久化
let s = session()
discard s.reqGet("https://httpbin.org/cookies/set/token/abc123")
discard s.reqGet("https://httpbin.org/cookies/set/uid/42")
let r = s.reqGet("https://httpbin.org/cookies")
echo r.json # {"cookies": {"token": "abc123", "uid": "42"}}
s.reqClose()
已验证:Session 自动保存响应的 Set-Cookie 并在后续请求中携带,支持多 Cookie 累积。
认证
let s = session(auth = ("user", "pass"))
let r = s.reqGet("https://httpbin.org/basic-auth/user/pass")
echo r.ok # true
s.reqClose()
自定义 Headers 持久化
var hdrs = {"X-Session-Id": "my-session"}.toTable
let s = session(headers = hdrs)
let r = s.reqGet("https://httpbin.org/headers")
echo r.json["headers"]["X-Session-Id"].getStr # "my-session"
s.reqClose()
RequestsClient 客户端
RequestsClient 是底层客户端对象,提供比顶层函数更精细的控制:
代理设置、Cookie 持久化、自定义超时等。
创建客户端
import requests
let client = newRequestsClient(
timeout = 30,
verify = true,
maxRedirects = 10,
)
客户端方法
| 方法 | 说明 | 测试 |
|---|---|---|
client.reqGet(url, hdrs, params, timeout) | GET 请求 | PASS |
client.reqPost(url, data, jsonBody, hdrs, timeout) | POST 请求 | PASS |
client.reqPut(url, data, jsonBody, hdrs, timeout) | PUT 请求 | PASS |
client.reqDelete(url, hdrs, timeout) | DELETE 请求 | PASS |
client.reqHead(url, hdrs, timeout) | HEAD 请求 | PASS |
client.reqPatch(url, data, jsonBody, hdrs, timeout) | PATCH 请求 | PASS |
client.reqOptions(url, hdrs, timeout) | OPTIONS 请求 | PASS |
client.setProxy(url) | 设置代理 | PASS |
client.doRequest(req) | 执行自定义 HttpReq | — |
client.downloadFile(url, destPath, hdrs, timeout) | 下载文件 | — |
client.reqClose() | 关闭客户端 | — |
代理设置
let client = newRequestsClient()
client.setProxy("http://proxy.example.com:8080")
let r = client.reqGet("https://httpbin.org/ip")
echo r.json
client.reqClose()
Cookie 持久化
let client = newRequestsClient()
discard client.reqGet("https://httpbin.org/cookies/set/ckey/cval")
let r = client.reqGet("https://httpbin.org/cookies")
echo r.json["cookies"].getStr # 包含 "ckey=cval"
client.reqClose()
数据模型
HttpResp — 响应对象
| 字段 | 类型 | 说明 |
|---|---|---|
code | int | HTTP 状态码 |
status | string | 完整状态行(如 "200 OK") |
headers | CaseInsensitiveTable | 响应头(大小写不敏感) |
body | string | 响应体 |
url | string | 最终请求 URL |
elapsed | float | 请求耗时(毫秒) |
encoding | string | 响应编码 |
reason | string | 状态原因短语 |
cookies | CookieJar | 响应设置的 Cookie |
history | seq[HttpResp] | 重定向历史 |
isRedirect | bool | 是否为重定向响应 |
便捷方法
| 方法 | 返回 | 说明 |
|---|---|---|
r.ok | bool | 状态码 200-299 返回 true |
r.text() | string | 返回响应体 |
r.json() | JsonNode | 解析 JSON 响应体 |
r.raiseForStatus() | — | 非 2xx 状态码抛出 HTTPError |
r.close() | — | 关闭响应 |
HttpReq — 请求对象
import requests
var req = newRequest("https://httpbin.org/post", rmPost)
req.data = "key=value"
req.headers["Content-Type"] = "application/x-www-form-urlencoded"
req.timeout = 10
req.allowRedirects = false
req.cookies.setCookie("session", "abc")
CaseInsensitiveTable — 大小写不敏感表
HTTP 头部专用数据结构,键名大小写不敏感。
let t = newCaseInsensitiveTable()
t["Content-Type"] = "application/json"
echo t["content-type"] # "application/json"
echo t["CONTENT-TYPE"] # "application/json"
echo t.containsKey("content-type") # true
echo t.getVal("missing", "default") # "default"
# 迭代
for k, v in t.items:
echo k, " = ", v
CookieJar — Cookie 容器
let jar = newCookieJar()
jar.setCookie("session", "abc123", domain = "example.com",
path = "/", secure = true, httpOnly = true)
echo jar.getCookie("session") # "abc123"
echo jar.getCookieHeader() # "session=abc123"
# 解析 Set-Cookie 头
jar.updateFromHeader("token=xyz; Path=/api; Secure; HttpOnly")
echo jar.getCookie("token") # "xyz"
jar.clear()
认证模块
BasicAuth
import requests
let auth = newBasicAuth("username", "password")
let (key, val) = auth.applyAuth()
# key = "Authorization"
# val = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
BearerAuth
let auth = newBearerAuth("my-token-123")
let (key, val) = auth.applyAuth()
# key = "Authorization"
# val = "Bearer my-token-123"
多态派发
var auth: AuthBase = newBasicAuth("u", "p")
let (k1, v1) = auth.applyAuth() # Basic ...
auth = newBearerAuth("tok")
let (k2, v2) = auth.applyAuth() # Bearer tok
工具函数
| 函数 | 说明 | 测试 |
|---|---|---|
buildURL(base, params) | 构建带查询参数的 URL,自动编码 | PASS |
quote(s) | URL 编码 | PASS |
unquote(s) | URL 解码 | PASS |
guessJSON(body) | 判断字符串是否为 JSON | PASS |
guessFilename(cd) | 从 Content-Disposition 提取文件名 | PASS |
parseHeaderLine(line) | 解析 HTTP 头行 | PASS |
requoteURI(url) | 重新编码 URI | PASS |
getEncodingFromHeaders(ct) | 从 Content-Type 提取 charset | PASS |
toHeadersTable(pairs) | 数组转 Table | PASS |
dictToJSON(data) | Table 转 JSON 字符串 | PASS |
mergeHeaders(base, ov) | 合并两个 Headers Table | PASS |
parseURL(url) | 解析 URL 为 (scheme, host, port, path, query, isSecure) | PASS |
getDefaultHeaders() | 获取默认请求头 | PASS |
URL 构建
var params = {"q": "hello world", "page": "1"}.toTable
echo buildURL("https://example.com/search", params)
# https://example.com/search?q=hello+world&page=1
echo buildURL("https://example.com?a=1", {"b": "2"}.toTable)
# https://example.com?a=1&b=2
编码检测
echo getEncodingFromHeaders("text/html; charset=utf-8") # "utf-8"
echo getEncodingFromHeaders("application/json") # "utf-8"
echo getEncodingFromHeaders("text/html; charset=gbk; x=y") # "gbk"
JSON 判断
echo guessJSON("""{"key": "value"}""") # true
echo guessJSON("""[1, 2, 3]""") # true
echo guessJSON("plain text") # false
echo guessJSON(" {\"a\":1}") # true
异常体系
| 异常类型 | 继承 | 触发场景 |
|---|---|---|
RequestException | CatchableError | 所有请求异常的基类 |
ConnectionError | RequestException | 网络连接失败 |
TimeoutError | RequestException | 请求超时 |
TooManyRedirects | RequestException | 重定向次数过多 |
HTTPError | RequestException | HTTP 错误状态码(raiseForStatus) |
URLRequired | RequestException | URL 缺失 |
MissingSchema | RequestException | URL 缺少协议 |
InvalidSchema | RequestException | 不支持的协议 |
InvalidURL | RequestException | URL 格式无效 |
SSLError | RequestException | SSL/TLS 错误 |
ProxyError | RequestException | 代理连接失败 |
ContentDecodingError | RequestException | 内容解码失败 |
ChunkedEncodingError | RequestException | 分块编码错误 |
异常处理示例
import requests
try:
let r = get("https://httpbin.org/status/500")
r.raiseForStatus()
except HTTPError as e:
echo "HTTP 错误: ", e.msg # "500 Internal Server Error"
try:
let r = get("https://invalid-host.example.com")
except ConnectionError as e:
echo "连接失败: ", e.msg
测试报告
全部 80+ 测试用例 通过,覆盖 17 个测试套件。
测试结果:0 失败 / 0 跳过 / 全部通过 ✅
测试套件一览
| 套件 | 用例数 | 结果 | 覆盖范围 |
|---|---|---|---|
| GET 请求 | 11 | ALL PASS | 简单 GET、JSON、参数、Headers、耗时、编码、404、500、禁用重定向 |
| POST 请求 | 4 | ALL PASS | 表单、JSON、空数据、自定义 Headers |
| PUT 请求 | 3 | ALL PASS | 表单、JSON、空数据 |
| DELETE 请求 | 2 | ALL PASS | 基本、带 Headers |
| HEAD 请求 | 2 | ALL PASS | 状态码、空 body |
| OPTIONS 请求 | 1 | ALL PASS | 状态码 |
| PATCH 请求 | 2 | ALL PASS | 数据、JSON |
| request() 通用方法 | 9 | ALL PASS | 全部 7 种方法 + params + JSON body |
| 认证 | 3 | ALL PASS | Basic Auth、Bearer Token、Session 认证 |
| 异常处理 | 4 | ALL PASS | raiseForStatus 200/500/404、错误消息 |
| Session 会话 | 10 | ALL PASS | 全部 7 种方法 + 自定义 Headers + Cookie 持久化 + 多 Cookie |
| RequestsClient | 9 | ALL PASS | 全部 7 种方法 + Cookie 持久化 + 代理 |
| CookieJar | 8 | ALL PASS | set/get/header/updateFromHeader/clear/属性 |
| CaseInsensitiveTable | 9 | ALL PASS | 存取/containsKey/del/getVal/len/keys/values/items/覆盖 |
| Auth 模块 | 3 | ALL PASS | BasicAuth/BearerAuth/多态 |
| Utils 工具函数 | 18 | ALL PASS | buildURL/编码/JSON判断/文件名/header解析/quote/parseURL等 |
| 集成测试 | 7 | ALL PASS | 完整流程、Cookie 端到端、User-Agent、HTML、UTF-8、超时 |
运行测试
# 启动测试服务器
python3 tests/server.py &
# 运行测试
nimble test
# 或手动编译
nim c -d:ssl --path:src -r tests/test_all.nim