Sign in

    #response —— 读全量的响应值

    Response 对应 axios 的 AxiosResponse:状态行、响应头、触发它的配置,以及未经解码的原始字节。它不抛 HttpError——能拿到 Response 就说明 HTTP 这一层已经成功(状态码校验在 client 包的管线上);怎么读由方法决定,没有「默认解码」。

    import { "q2316367743/moonhttp/response", }

    通常不必直接 import 本包:根包 @moonhttp 已把 Response 再导出。

    #读法

    方法说明
    bytes()原始响应字节,不解码(二进制内容、想自己按别的编码解时用)
    text()按 config.response_encoding 解成文本;解码一律 lossy(坏字节 → U+FFFD),不抛错
    json()先按 text() 解码再 @json.parse;解析失败抛 @json.ParseError,不是 HttpError
    content_length()响应体字节长度,等价 bytes().length()
    is_success()状态码是否 2xx(给自定义了 validate_status 放行 3xx/4xx 的场景用)

    #改写入口(供响应拦截器)

    方法说明
    with_status(status)换状态码(不重跑状态码校验)
    with_headers(headers)整体替换响应头
    with_body(bytes)换响应体字节
    with_text(text)换响应体文本,按 response_encoding 编码——与 text() 读方向对称,with_text(response.text()) 恒等
    with_json(json)换 JSON 载荷(写回的是 JSON 文档:Json::String("x") 写成 "x" 带引号)

    五个方法都返回新值(Response 是不可变值),都不改响应头里的 Content-Length。

    #用法

    let resp = api.get("/user/1")
    println(resp.status) // 200
    let user = resp.json()
    println(resp.text())

    raw 字段私有、跨包构造只有 Response::new 一个口(client 包拼响应走它):这是为了让「怎么读」只有一个落点,response_encoding 才不会名存实亡。

    实现取舍见 docs/01-architecture.md。

    Response

    pub(all) struct Response {
    status : Int
    status_text : String
    headers :
    Headers

    config :
    Config

    // private fields
    } derive(
    Debug
    )

    对外的响应对象,对应 axios 的 AxiosResponse。

    响应体只保留原始字节(私有字段 raw),怎么读由三个方法决定:
    • bytes():原始字节,二进制内容或「想用别的编码自己解」走这里;
    • text():按 config.response_encoding 解码成文本(lossy);
    • json():先按同一编码解成文本,再交给 @json.parse。

    这是与 axios 的一处刻意差异:axios 的 res.data 是 any,由 responseType / transformResponse / forcedJSONParsing 猜出来;本项目不猜——没有「默认解码」 这一说,读法摆在方法上。猜内容类型在静态类型下只会把错误推迟 (text/plain 的 123 被解成数字那类静默错误),多一次显式调用反而是更便宜的。

    字段私有是为了让「怎么读」只有一个落点:raw 一旦公开,调用方就会各自 按自己的编码解一遍,response_encoding 这个配置项也就名存实亡了。

    Response::bytes

    fn Response::bytes(self : Response) -> Bytes

    原始响应字节,不做任何解码。

    需要精确字节、二进制内容(图片、压缩包),或想按另一种编码自己解时用它; content_length() 数的也是这份字节。文本请用 text()。

    Response::content_length

    fn Response::content_length(self : Response) -> Int

    响应体的字节长度,等价于 bytes().length()。

    Response::is_success

    fn Response::is_success(self : Response) -> Bool

    状态码是否落在 2xx。

    注意这与「请求是否成功」不是一回事:request 只在校验规则放行时才返回响应, 所以能拿到 Response 通常已经表示成功;这个方法是给 自定义了 validate_status(放行了 3xx/4xx)的场景用的。

    Response::json

    把响应体当 JSON 解析:先按 text() 那套规则(config.response_encoding) 解成文本,再交给 @json.parse(Json 是内建类型,不必 import 额外的东西)。

    解析失败抛的是 @json.ParseError(带出错位置),不是 HttpError—— 「HTTP 这一层出没出问题」与「正文是不是合法 JSON」是两类事:状态码不合规 早在 request 里就抛过了,能拿到 Response 说明这一层已经成功; 把解析错误混进 HttpError 只会让两边的分类都变模糊。不想处理解析错误的 调用方用 try! response.json(),与手写 try! @json.parse(response.text()) 等价。

    与 axios 的差别同样在这里:只有显式调用才解析,且不看 Content-Type 猜——123、true、"x" 这些纯文本本身就是合法 JSON 文本,猜错的后果是 静默的(文本被解成数字)。判断「这到底是不是 JSON」交给知道上下文的那一层。

    Response::new

    fn Response::new(status : Int, status_text : String, headers :
    Headers
    , raw : Bytes, config :
    Config
    ) -> Response

    从「零件」拼出一份 Response——本包唯一的公开构造入口。

    raw 是私有字段,跨包写不出记录字面量,所以拼响应的活只能落在本包: client 包的三条路(读全量的正常路径、流式入口的错误体、传输失败时 挂到错误上的现场)都经这里。

    Response::text

    fn Response::text(self : Response) -> String

    按 config.response_encoding 把响应体解成文本(编码未设置时按 UTF-8, 与 defaults() 里显式设的默认值一致)。

    解码一律 lossy:该编码下非法的字节解成替换字符 U+FFFD,不抛错 ——响应正文由服务端说了算,为了几个坏字节把整个响应判成失败得不偿失。 需要精确字节读 bytes()。

    解码是按需做的:Response 里只有字节、不缓存文本,调几次就解几次。 同一份响应要反复读文本时(例如在循环里),自己解一次存起来更划算。

    Response::to_repr

    Response::with_body

    fn Response::with_body(self : Response, body : Bytes) -> Response

    换一份响应体字节,返回新响应:bytes() / text() / json() / content_length() 读到的都是新字节(解码才看 response_encoding)。

    Response::with_headers

    换一组响应头,返回新响应。整体替换而不是合并——要合并先自己 merge。

    Response::with_json

    fn Response::with_json(self : Response, data : Json) -> Response

    换一份响应体载荷(Json),返回新响应:先 stringify() 成规范 JSON 文本,再按 config.response_encoding 编成字节(与 with_text 同一口径)。

    这是「响应后处理」的写回入口,对应 axios 的 transformResponse 里那句 res.data = ...:解析用 json(),处理完用这个方法写回,调用方再读到的 就是处理过的载荷。JSON 是静态类型下「任意数据」的落点,所以这一条比 with_text 更贴这种用法。

    两个语义要注意:
    • 写回的是载荷,也就是一份 JSON 文档:Json::String("x") 写成 "x" (带引号),不是裸的 x。要写原文用 with_text。
    • stringify() 的输出是紧凑形式(无缩进、无多余空白),所以「只把正文规范化 一遍」也是它的合法用法。

    Response::with_status

    fn Response::with_status(self : Response, status : Int) -> Response

    换一个状态码,返回新响应。

    给响应拦截器用:把「HTTP 上是 200、业务上是失败」的响应改判,或者把服务端 写错的状态码归一。status_text 不跟着变——它只是状态行的原文。

    改状态码不会重新跑状态码校验:validate_status 早在拦截器之前就跑完了, 返回的响应原样交给调用方(要让它变成失败,请在拦截器里抛 HttpError)。

    Response::with_text

    fn Response::with_text(self : Response, body : String) -> Response

    换一份响应体文本,返回新响应。编码按 config.response_encoding(未设置时 按 UTF-8),与 text() 的读方向同一个编码——with_text(response.text()) 因此是恒等的,改写不会把非 UTF-8 的正文写坏(latin1 的 é 写回去还是 0xE9,详细理由见 @util.encode_body)。

    该编码装不下的码元写成一个 ?(不抛错,与解码的 lossy 口径一致)。 要精确字节、或要一份别的编码的正文,用 with_body 自己编。

    Source Files