为你自己学Go
  • README
  • PART01.Web框架概览
    • 1.01 Web框架概览:学习路线
    • 1.02 Web框架概览-Beego框架分析
    • 1.03 Web框架概览-GIN框架分析
    • 1.04 Web框架概览-Iris框架分析
    • 1.05 Web框架概览-Echo框架分析
  • PART02.Server
    • 2.01 Server详解与面试要点
  • PART03.路由树
    • 3.01 路由树-Beego&GIN&Echo实现与设计总结
    • 3.02 路由树-全静态匹配
    • 3.03 路由树-TDD起步
    • 3.04 路由树-静态匹配测试用例
    • 3.05 路由树-静态匹配之路由查找
    • 3.06 路由树-静态匹配之集成Server
    • 3.07 路由树-通配符匹配之路由注册
    • 3.08 路由树-通配符匹配之路由查找与测试
    • 3.09 路由树-参数路径之基本注册和查找
    • 3.10 路由树-参数路径之校验
    • 3.11 路由树-参数路径之参数值
    • 3.12 路由树-总结与面试要点
  • PART04.课后复习
    • 4.01 课后复习-Server
    • 4.02 课后复习-Route
  • PART05.Context
    • 5.01 Context-简介
    • 5.02 Context-Beego Context设计分析
    • 5.03 Context-Gin Context设计分析
    • 5.04 Context-Echo和Iris的Context设计分析
    • 5.05 Context-处理输入输出总结
    • 5.06 Context-处理输入之Body输入
    • 5.07 Context-处理输入之表单输入
    • 5.08 Context-处理输入之查询参数、路径参数和StringValue
    • 5.09 Context-处理输出
    • 5.10 Context-总结与面试要点
  • PART06.AOP
    • 6.01 AOP简介与不同框架设计概览
    • 6.02 AOP设计方案-Middleware
  • PART07.Middleware
    • 7.01 Middleware-AccessLog
    • 7.02 Middleware-Trace简介和OpenTelemetry
    • 7.03 Middleware-OpenTelemetry测试
    • 7.04 Middleware-OpenTelemetry总结
    • 7.05 Prometheus详解
    • 7.06 Middleware-Prometheus
    • 7.07 Middleware-错误页面
    • 7.08 Middleware-从panic中恢复
    • 7.09 Middleware总结和面试
  • PART08.Review
    • 8.01 课后复习-AOP
    • 8.02 课后复习-Context
    • 8.03 课后复习-Middleware-AccessLog
  • PART09.Appendix
    • 附录1.责任链模式
    • 附录2.生成器模式
    • 附录3.函数选项模式
  • xiaochengxu
    • 01.原力去水印
    • 02.KeePass密码管理:安全轻松的管理您的密码
Powered by GitBook
On this page
  • PART1. 需求概述
  • PART2. 实现
  • 2.1 基本构型
  • 2.2 捕获panic并篡改响应
  • 2.3 捕获panic时记录日志
  • PART2. 测试
  1. PART07.Middleware

7.08 Middleware-从panic中恢复

本节课工程结构如下:

(base) yanglei@yuanhong 06-recoverPanic % tree ./
./
├── context.go
├── context_test.go
├── go.mod
├── go.sum
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── matchNode.go
├── middleware.go
├── middleware_test.go
├── middlewares
│   ├── access_log
│   │   ├── accessLog.go
│   │   ├── accessLog_test.go
│   │   └── middlewareBuilder.go
│   ├── err_page
│   │   ├── middlewareBuilder.go
│   │   └── middleware_test.go
│   ├── open_telemetry
│   │   ├── docker-compose.yaml
│   │   ├── middlewareBuilder.go
│   │   └── middleware_test.go
│   └── prometheus
│       ├── middlewareBuilder.go
│       └── middleware_test.go
├── node.go
├── option.go
├── router.go
├── router_test.go
├── safeContext.go
├── serverInterface.go
└── stringValue.go

5 directories, 27 files

PART1. 需求概述

框架使用者在他们自己的代码中如果不小心触发了一些panic,那么前端会收到一个错误,且同时整个进程也就结束了.正常情况下,即使某个请求触发了panic,也不应该让整个进程都挂了,因为还有其他不会触发panic的路由是可用的.因此我们需要写一个捕获panic的中间件并返回一个固定的错误,确保即使框架使用者触发了panic,整个进程不会宕掉,

PART2. 实现

2.1 基本构型

middleware/recover_panic/middlewareBuilder.go:

package recover_panic

import "web"

// MiddlewareBuilder 捕获panic中间件的构建器
type MiddlewareBuilder struct {
	
}

// Build 构建捕获panic中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			next(ctx)
		}
	}
}

2.2 捕获panic并篡改响应

middleware/recover_panic/middlewareBuilder.go:

package recover_panic

import "web"

// MiddlewareBuilder 捕获panic中间件的构建器
type MiddlewareBuilder struct {
	StatusCode int    // StatusCode 捕获panic时的响应状态码
	Data       []byte // Data 捕获panic时的响应数据
}

// Build 构建捕获panic中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			// 捕获panic并篡改响应
			defer func() {
				// Tips: 这里的err的类型是ary,而不是error
				if err := recover(); err != nil {
					ctx.RespStatusCode = m.StatusCode
					ctx.RespData = m.Data
				}
			}()
			
			next(ctx)
		}
	}
}

2.3 捕获panic时记录日志

middleware/recover_panic/middlewareBuilder.go:

package recover_panic

import "web"

// MiddlewareBuilder 捕获panic中间件的构建器
type MiddlewareBuilder struct {
	StatusCode int                    // StatusCode 捕获panic时的响应状态码
	Data       []byte                 // Data 捕获panic时的响应数据
	LogFunc    func(ctx *web.Context) // LogFunc 捕获panic时的日志记录函数 (记录整个ctx)
	// LogFunc    func(err any)          // LogFunc 捕获panic时的日志记录函数 (记录panic的内容)
	// LogFunc    func(stack string)     // LogFunc 捕获panic时的日志记录函数 (记录调用栈)
}

// Build 构建捕获panic中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			// 捕获panic 篡改响应 并记录日志
			defer func() {
				// Tips: 这里的err的类型是ary,而不是error
				if err := recover(); err != nil {
					ctx.RespStatusCode = m.StatusCode
					ctx.RespData = m.Data

					// 记录日志
					if m.LogFunc != nil {
						m.LogFunc(ctx)
					}
				}
			}()
			
			next(ctx)
		}
	}
}

这里的日志记录函数,不一定非要记录Context,也可以记录panic的内容,或者记录发生panic时的堆栈信息

PART2. 测试

middleware/recover_panic/middleware_test.go:

package recover_panic

import (
	"fmt"
	"testing"
	"web"
)

// Test_MiddlewareBuilder 测试捕获panic中间件构造器
func Test_MiddlewareBuilder(t *testing.T) {
	// 创建捕获panic中间件构造器
	builder := &MiddlewareBuilder{
		StatusCode: 500,
		Data:       []byte("panic error"),
		LogFunc: func(ctx *web.Context) {
			fmt.Printf("panic路径: %s\n", ctx.Req.URL.Path)
		},
	}

	// 构建捕获panic中间件
	option := web.ServerWithMiddleware(builder.Build())

	// 创建web服务器
	server := web.NewHTTPServer(option)

	// 创建HandleFunc
	handleFunc := func(ctx *web.Context) {
		panic("test panic")
	}

	// 注册路由并启动服务器
	server.GET("/user", handleFunc)
	server.Start(":8080")
}

运行结果:

=== RUN   Test_MiddlewareBuilder
panic路径: /user

而我们的设计,是否使用捕获panic的逻辑与核心逻辑就完全无关了.而且完全没有侵入任何核心逻辑.还是那句话,无侵入式的设计,才是高明的设计.

Last updated 9 months ago

Beego就是一种侵入式的设计,Beego中的和实际上都是和核心逻辑耦合在一起的,并不是一种良构的设计.

RecoverPanic
RecoverFunc
panic页面