Closed xmx closed 2 years ago
这里有两个问题:
Go
标准库 net/http
的限制:http.ResponseWriter.WriteHeader(statusCode int)
原则上只能调用一次;虽然可以调用多次,但只有第一次生效,对于后续的调用,http.Server
仅会打印一条错误日志。所以 HandleError
的正确实现是:要检查 WriteHeader
是否已经调用(ship.Context.IsResponded()
),只有未调用的情况下,才可以向客户端返回新的状态码。ship.Context.JSON(statusCode int, data interface{})
当前是假定 data
必须可以被 JSON 序列化,否则可能会出现问题。这个可以稍后会修复一下,以便支持 data
不能被 JSON 序列化的情况。标准库*http.response确实是:只允许状态码设置一次,所以我也简单观察了以下常见的几种 go web 框架针对该种类型错误的处理方式:
net/http
实现,超出三界之外不在五行之中🤪,没继续看实现。gin
框架错误机制稍微和ship
不太一样,这个地方会panic
,依靠recovery中间件兜底,但是由此可见也是做了特殊处理。专门针对状态码不可重复写做了处理:对标准库的 http.ResponseWriter
进行了包装ship
类似,同样也是对对标准库的 http.ResponseWriter
进行了包装大概逻辑都是包装了以下标准库的http.ResponseWriter
,加入status
字段用于多次修改状态码,等到到业务代码处理完,最后调用标准库http.Write
的时候才写入最终的状态码
针对 ship.Context.JSON(statusCode int, data interface{})
假定 data
必须可以被 JSON 序列化 的情况,已经优化,当前版本 v5.1.2
已经可以正确处理 不能被 JSON 序列化的 data
了。
// go.mod
module myapp
require github.com/xgfone/ship/v5 v5.1.2
// main.go
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/xgfone/ship/v5"
)
func main() {
s := ship.New()
s.HandleError = func(c *ship.Context, err error) {
// 只有在未应答(也即是没有调用 WriteHeader)的情况下,
// 我们才能继续使用类似 Text、JSON、XML 等应答方法。
if !c.IsResponded() {
// 如果出现错误:http状态码为400
c.Text(http.StatusBadRequest, err.Error())
}
// 记录错误信息到日志中
log.Printf("method=%s, path=%s, code=%d, err=%s",
c.Method(), c.Path(), c.StatusCode(), err)
}
s.Route("/demo").GET(func(c *ship.Context) error {
wrong := json.RawMessage("a:b") // 错误的JSON数据
return c.JSON(http.StatusOK, wrong) // 如果执行正常:http状态码200
})
ship.StartServer(":8080", s)
}
$ curl -i http://127.0.0.1:8080/demo
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=UTF-8
Date: Wed, 29 Dec 2021 13:49:58 GMT
Content-Length: 110
json: error calling MarshalJSON for type json.RawMessage: invalid character 'a' looking for beginning of value
Go 标准库实现的 HTTP Server,对于 ResponseWriter
仅支持且实现了 应答行(即状态码)
、应答头(即 Header)
、应答体(即 Body)
的 发送 (分别对应 WriteHeader
、Write
两个方法),未提供发送的状态。而对于框架而言,有两个状态几乎是很重要的:
WriteHeader
是否已经调用? 这是为了避免多次调用 WriteHeader
,因为标准库不支持多次调用,只有第一次才有效。这个很容易理解:因为不能发送了应答Header之后,再发送状态码。WriteHeader
已经调用,则应答状态码是多少? 这可以用来记录日志。由于标准库未提供,因此,几乎大部分基本于标准库的HTTP框架都会捕获 ResponseWriter
,然后提供上述发送状态查询,echo
和 ship
都是如此。换句话说就是,只要是基于 Go 标准库 net/http
的 HTTP 框架,都逃不出这个。
所以,在上面的 HandleError
样例中,要先检查 WriteHeader
是否已经调用过了;只有在没有调用的情况下,执行类似 Text
的应答方法才有意义。
ship 版本
v5.1.1
代码
curl 测试输出(问题在响应报文的状态码)
期望