nim-requests

Python Requests 风格的 Nim 2 HTTP 客户端库

Nim 2.2+ v1.0.0 MIT

概述

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.okr.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 — 响应对象

字段类型说明
codeintHTTP 状态码
statusstring完整状态行(如 "200 OK")
headersCaseInsensitiveTable响应头(大小写不敏感)
bodystring响应体
urlstring最终请求 URL
elapsedfloat请求耗时(毫秒)
encodingstring响应编码
reasonstring状态原因短语
cookiesCookieJar响应设置的 Cookie
historyseq[HttpResp]重定向历史
isRedirectbool是否为重定向响应

便捷方法

方法返回说明
r.okbool状态码 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)判断字符串是否为 JSONPASS
guessFilename(cd)从 Content-Disposition 提取文件名PASS
parseHeaderLine(line)解析 HTTP 头行PASS
requoteURI(url)重新编码 URIPASS
getEncodingFromHeaders(ct)从 Content-Type 提取 charsetPASS
toHeadersTable(pairs)数组转 TablePASS
dictToJSON(data)Table 转 JSON 字符串PASS
mergeHeaders(base, ov)合并两个 Headers TablePASS
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

异常体系

异常类型继承触发场景
RequestExceptionCatchableError所有请求异常的基类
ConnectionErrorRequestException网络连接失败
TimeoutErrorRequestException请求超时
TooManyRedirectsRequestException重定向次数过多
HTTPErrorRequestExceptionHTTP 错误状态码(raiseForStatus)
URLRequiredRequestExceptionURL 缺失
MissingSchemaRequestExceptionURL 缺少协议
InvalidSchemaRequestException不支持的协议
InvalidURLRequestExceptionURL 格式无效
SSLErrorRequestExceptionSSL/TLS 错误
ProxyErrorRequestException代理连接失败
ContentDecodingErrorRequestException内容解码失败
ChunkedEncodingErrorRequestException分块编码错误

异常处理示例

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 请求11ALL PASS简单 GET、JSON、参数、Headers、耗时、编码、404、500、禁用重定向
POST 请求4ALL PASS表单、JSON、空数据、自定义 Headers
PUT 请求3ALL PASS表单、JSON、空数据
DELETE 请求2ALL PASS基本、带 Headers
HEAD 请求2ALL PASS状态码、空 body
OPTIONS 请求1ALL PASS状态码
PATCH 请求2ALL PASS数据、JSON
request() 通用方法9ALL PASS全部 7 种方法 + params + JSON body
认证3ALL PASSBasic Auth、Bearer Token、Session 认证
异常处理4ALL PASSraiseForStatus 200/500/404、错误消息
Session 会话10ALL PASS全部 7 种方法 + 自定义 Headers + Cookie 持久化 + 多 Cookie
RequestsClient9ALL PASS全部 7 种方法 + Cookie 持久化 + 代理
CookieJar8ALL PASSset/get/header/updateFromHeader/clear/属性
CaseInsensitiveTable9ALL PASS存取/containsKey/del/getVal/len/keys/values/items/覆盖
Auth 模块3ALL PASSBasicAuth/BearerAuth/多态
Utils 工具函数18ALL PASSbuildURL/编码/JSON判断/文件名/header解析/quote/parseURL等
集成测试7ALL PASS完整流程、Cookie 端到端、User-Agent、HTML、UTF-8、超时

运行测试

# 启动测试服务器
python3 tests/server.py &

# 运行测试
nimble test

# 或手动编译
nim c -d:ssl --path:src -r tests/test_all.nim