进阶 www.iris-go.com 2026-10-20 02:39:14 · 19 阅读

第16章 错误处理

错误处理

MVC 应用程序或 Controller 的错误处理方式取决于错误类型及其来源。在本节中,你将学习如何根据错误类型(例如 HTTP 错误如 404、方法返回的响应错误或渲染响应失败等)来处理各类错误。必要时,Controller 可以同时使用所有这些方法。

HTTP 错误

我们从最关键的部分开始:针对每个 Controller 处理 HTTP 错误。 要注册 HTTP 错误处理器,Controller(或其嵌入字段之一)必须包含一个名为 HandleHTTPError 的方法。该方法可以接受任意类型的参数并输出结果以渲染响应,与 Controller 中的其他方法类似。当发生 HTTP 错误(客户端 4xx 或服务器 5xx)时,框架会自动调用 HandleHTTPError。

示例代码:

以下代码片段会在遇到 404 Not Found 时渲染 "errors/404.html",在遇到 500 Internal Server Error 时渲染 "errors/500.html",依此类推。

func (c *Base) HandleHTTPError(err mvc.Err, statusCode mvc.Code) mvc.View {
    if err != nil {
        // 对错误进行处理,
        // 例如 view.Data = MyPageData{Message: err.Error()}
    }

	code := int(statusCode) // 将其转换为 int。

	return mvc.View{
		Code: code,
		Name: fmt.Sprintf("errors/%d.html", code),
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

mvc.Code 类型的输入参数是可选的,但遵循它是一个好习惯。你也可以注册一个 Context,并通过 ctx.GetStatusCode() 获取其错误码。

该函数可以像其他 Controller 方法一样接受依赖项和输出值,但需注意,如果你注册的依赖项仅依赖于成功请求(200...)。

让我们编写一个能触发 HandleHTTPError 的方法。这完全是可选的,因为在发生 HTTP 错误(例如客户端 404)时,框架会自动调用 HandleHTTPError。

func (c *UserController) GetError() mvc.Result {
	return mvc.View{
		// 在 HandleHTTPError 方法中,分别映射为 mvc.Code 和 mvc.Err。
		Code: iris.StatusBadRequest,
		Err:  fmt.Errorf("custom error"),
    }

    // 或者使用 mvc.Response{Code: ... Err: ...}
    // 或者使用 ctx.StatusCode(...) 和 ctx.SetErr(...)
    // 或者返回 (int, error)。
}
1
2
3
4
5
6
7
8
9
10
11
另请注意,如果在同一个 Party 中注册多个 HandleHTTPError,需要像已认证控制器示例那样使用 RouteOverlap 特性。 自定义错误 Controller 的方法可以返回一个自定义结构体,由它根据给定数据渲染响应。这个错误可以是一个 HTTP 错误,也可以是带 2xx 状态码的视图,或者是带 3xx 状态码的重定向响应。 这可以通过自定义 mvc.Result 或 mvc.Preflight 实现(前面章节已经演示过)。 使用 mvc.Result 当 Controller 的方法返回 mvc.Result 时,这是一种不错的处理方式。 我们来创建一个自定义 Go 结构体,其中包含渲染响应所需的数据。 ```go type errorResponse struct { Code int Message string } ``` 通过 Dispatch(iris.Context) 方法实现 mvc.Result。 ```go func (e errorResponse) Dispatch(ctx iris.Context) { view := mvc.View{ Code: e.Code, Data: e, Name: fmt.Sprintf("errors/%d.html", e.Code), } view.Dispatch(ctx) } ``` 记住:mvc.View、Response 等最终都是 mvc.Result,因此即使某个方法本身无法直接输出内容(比如这个 Dispatch 方法),也可以调用它们的 Dispatch 方法来渲染。 也可以只用 Context 的方法来实现: ```go func (e errorResponse) Dispatch(ctx iris.Context) { viewName := fmt.Sprintf("errors/%d.html", e.Code) ctx.StatusCode(e.Code) ctx.View(viewName, e) } ``` 用法 是时候使用我们的 errorResponse 了。我们来设计一个返回 mvc.Result 的 GetBy 方法,当"数据库"中找不到 "user" 时,它返回一个 errorResponse。 ```go func (c *UserController) GetBy(id uint64) mvc.Result { user, found, err := c.DB.Single( "SELECT * FROM users WHERE user_id=? LIMIT 1", id) if !found { return errorResponse{ Code: iris.StatusNotFound, Message: "User Not Found", } } // [...] } ``` 使用 mvc.Preflight 这是一种好的方法,适用于当返回值的类型可能实现也可能不实现 mvc.Result 或 mvc.Preflight 接口时。例如,一个单独的响应既可以输出数据,也可以输出错误。甚至返回完全不同的类型也是允许的,比如在处理有效请求时返回用户结构体(默认 JSON),而在失败时返回 userError mvc.Preflight。关于输出值的更多信息,请参见依赖注入部分。 go ```go // 用于 JSON 结果的通用响应类型 type response struct { ID uint64 `json:"id,omitempty"` Data interface{} `json:"data,omitempty"` Code int `json:"code,omitempty"` Message string `json:"message,omitempty"` Timestamp int64 `json:"timestamp,omitempty"` } ``` 1
2
3
4
5
6
7
8
实现 mvc.Preflight 接口,该接口将在渲染响应之前执行。它可以在对象渲染前对其进行操作,或者通过返回 iris.ErrStopExecution 错误来自行处理渲染。 go ```go func (r response) Preflight(ctx iris.Context) error { r.Timestamp = time.Now().Unix() if r.Code > 0 { // 你可以调用 ctx.View 或 mvc.View{...}.Dispatch // 在 Code >= 400 时渲染 HTML // 但为了不让最终结果表现为 JSON 的响应, // 你必须返回 iris.ErrStopExecution 错误。 // 示例: if r.Code >= 400 && ctx.GetContentTypeRequested() == "text/html" { // 如果 code 表示客户端或服务器错误, // 使用 mvc.View 渲染模板(也可以使用 ctx.View)。 mvc.View{ /* 调用 ctx.StatusCode */ Code: r.Code, /* 将 r.Data 中的任意部分用作模板数据 或者将整个 "response" 作为其数据。 */ Data: r, /* 根据错误自动选择模板 (仅为示例说明) */ Name: fmt.Sprintf("errors/%d.html", r.Code), }.Dispatch(ctx) return iris.ErrStopExecution } // 否则,仅根据给定结构体的值写入状态码, // 参见下面的 Controller 方法。 ctx.StatusCode(r.Code) } return nil } ``` 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
使用 我们可以在 Controller 的方法中使用自定义的响应类型。User 结构体会被嵌入到我们的响应中。Data 字段。 gotype User struct { ID uint64 `json:"id"` } func (c *Controller) GetBy(userid uint64) response { user, found, err := c.DB.Single( "SELECT * FROM users WHERE user_id=? LIMIT 1", id) if !found { return response{ Code: iris.StatusNotFound, Message: "User Not Found", } } return response{ ID: userid, Data: User{ ID: userid, }, } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
记住:一个结构体可以同时实现 mvc.Result 和 mvc.Preflight 接口。如果 Preflight 没有返回 iris.ErrStopExecution,那么流程会继续执行 Dispatch (mvc.Result) 方法。这是底层代码。 上面的示例在客户端接受 HTML 时会渲染 HTML 错误页面,否则会将错误和结果都渲染为 JSON。在设计供第三方程序员使用的 API 时,这是推荐的方法。 劫持 Controller 方法的结果 对于高级场景,还有另一种选择。当你需要操作、更改或记录每个 Controller 方法的返回值时(针对单个 MVC Application 或全局),可以注册自定义的 ResultHandler。 示例代码: gopackage main import ( "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/mvc" ) func main() { app := iris.New() app.RegisterView(iris.HTML("./views", ".html")) // 劫持每个方法的输出值(也可以按部分使用)。 app.ConfigureContainer(). UseResultHandler(func(next iris.ResultHandler) iris.ResultHandler { return func(ctx iris.Context, v interface{}) error { switch val := v.(type) { case errorResponse: return next(ctx, errorView(val)) default: return next(ctx, v) } } }) m := mvc.New(app) m.Handle(new(controller)) app.Listen(":8080") } func errorView(e errorResponse) mvc.Result { switch e.Code { case iris.StatusNotFound: return mvc.View{Code: e.Code, Name: "404.html", Data: e} default: return mvc.View{Code: e.Code, Name: "500.html", Data: e} } } type errorResponse struct { Code int Message string } type controller struct{} type user struct { ID uint64 `json:"id"` } func (c *controller) GetBy(userid uint64) interface{} { if userid != 1 { return errorResponse{ Code: iris.StatusNotFound, Message: "User Not Found", } } return user{ID: userid} }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

评论 (0)