为你自己学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 检测响应码并渲染对应的错误页面
  • 2.3 注册响应码与错误页面
  • PART3. 测试
  1. PART07.Middleware

7.07 Middleware-错误页面

本节课工程结构如下:

(base) yanglei@yuanhong 05-errPage % 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
│   ├── 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

4 directories, 25 files

PART1. 需求概述

我们通常有一个需求:如果一个响应返回了404,那么应该显示一个错误页面,停留在这个错误页面几秒钟后,重定向到一个默认页面,比如说重定向到首页

但有一个棘手的问题在于:不是所有的404都是要重定向的.比如说你是异步加载数据的RESTful请求,在打开页面之后异步加载用户详情,即便 404了也不应该重定向.因为请求API,404了就是404了,不需要你重定向到某个页面上.你的客户端也不会用你的404页面

目前我们没有设计可路由的中间件,因此没有办法给使用者提供一个入口,该入口用于控制某个路由是否需要重定向到某个页面.

此处我们先实现一个比较粗暴的设计:当检测到响应码为某个特定值时,篡改响应为某个错误页面

PART2. 实现

2.1 基本构型

这个都写了N遍了,没什么要解释的

middleware/err_page/middlewareBuilder.go:

package err_page

import "web"

// MiddlewareBuilder 错误页面中间件构造器
type MiddlewareBuilder struct {
}

// Build 构造错误页面中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			next(ctx)
		}
	}
}

2.2 检测响应码并渲染对应的错误页面

那首先肯定是需要一个map用于记录响应码与其对应错误页面的映射关系:

middleware/err_page/middlewareBuilder.go:

package err_page

import "web"

// MiddlewareBuilder 错误页面中间件构造器
type MiddlewareBuilder struct {
	respPages map[int][]byte // respPages 用于存储响应码与其对应的错误页面 其中key为响应码 value为错误页面的内容
}

// Build 构造错误页面中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			next(ctx)
		}
	}
}

接下来的操作就比较简单了,判断响应码是否为需要渲染错误页面的响应码,如果是,则渲染对应的错误页面即可:

middleware/err_page/middlewareBuilder.go:

package err_page

import "web"

// MiddlewareBuilder 错误页面中间件构造器
type MiddlewareBuilder struct {
	respPages map[int][]byte // respPages 用于存储响应码与其对应的错误页面 其中key为响应码 value为错误页面的内容
}

// Build 构造错误页面中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			next(ctx)

			// 判断响应码是否为需要篡改响应的响应码 如果是则篡改响应
			respPage, ok := m.respPages[ctx.RespStatusCode]
			if ok {
				ctx.RespData = respPage
			}
		}
	}
}

其实到这里主流程就实现完毕了已经.但还有一些边缘问题,例如:

  • 缺少一个用于让框架的使用者注册自己的响应码和错误页面的入口

  • 缺少初始化MiddlewareBuilder的过程

    • 这里因为MiddlewareBuilder. respPages是一个需要初始化的数据类型,因此

2.3 注册响应码与错误页面

这2个缺陷实现起来也很简单

middleware/err_page/middlewareBuilder.go:

package err_page

import "web"

// MiddlewareBuilder 错误页面中间件构造器
type MiddlewareBuilder struct {
	respPages map[int][]byte // respPages 用于存储响应码与其对应的错误页面 其中key为响应码 value为错误页面的内容
}

// Build 构造错误页面中间件
func (m *MiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			next(ctx)

			// 判断响应码是否为需要篡改响应的响应码 如果是则篡改响应
			respPage, ok := m.respPages[ctx.RespStatusCode]
			if ok {
				ctx.RespData = respPage
			}
		}
	}
}

// AddCode 添加响应码与其对应的错误页面
func (m *MiddlewareBuilder) AddCode(status int, page []byte) *MiddlewareBuilder {
	if m.respPages == nil {
		m.respPages = make(map[int][]byte)
	}
	m.respPages[status] = page
	
	// Tips: 此处返回 *MiddlewareBuilder 是为了支持链式调用
	return m
}

// NewMiddlewareBuilder 初始化错误页面中间件构造器
func NewMiddlewareBuilder() *MiddlewareBuilder {
	return &MiddlewareBuilder{
		respPages: make(map[int][]byte),
	}
}

PART3. 测试

middleware/err_page/middleware_test.go:

package err_page

import (
	"testing"
	"web"
)

// Test_MiddlewareBuilder 测试错误页面中间件构造器
func Test_MiddlewareBuilder(t *testing.T) {
	// 创建中间件构建器
	builder := NewMiddlewareBuilder()
	builder.
		AddCode(404, []byte(`
<html>
	<head>
		<title>404 Not Found</title>
	</head>

	<body>
		<h1>404 Not Found</h1>
	</body>
</html>
`)).
		AddCode(500, []byte(`
<html>
	<head>
		<title>500 Internal Server Error</title>
	</head>

	<body>
		<h1>500 Internal Server Error</h1>
	</body>
</html>
`))

	// 创建中间件Option
	options := web.ServerWithMiddleware(builder.Build())

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

	// 启动服务器
	server.Start(":8080")
}

这个测试用例中,我们都不需要注册路由,只需要随便访问一个路由,看是否能渲染我们定义的错误页面即可:

TODO:做完作业考虑如何实现针对指定路由或指定方法的错误页面中间件

Last updated 9 months ago

篡改的404页面