为你自己学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. 实现HTTPServer.serve()方法
  • PART2. 修改HandleFunc类型的入参
  • 2.1 修改入参类型为*Context
  • 2.2 修改其他受影响的地方
  • PART3. 测试
  • PART4. 修改HTTPServer的成员属性
  • 附录
  • 需要课后尝试的内容
  • 何时将成员属性类型定义为指针类型?何时将成员属性类型定义为结构体类型?
  1. PART03.路由树

3.06 路由树-静态匹配之集成Server

本节课工程结构如下:

(base) yanglei@yuanhong 11-embeddingRouterIntoServer % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── node.go
├── router.go
├── router_test.go
└── serverInterface.go

0 directories, 8 files

PART1. 实现HTTPServer.serve()方法

之前的课程中说过,该方法的职责为:查找路由树并执行命中的业务逻辑.我们也已经实现了router.findRoute().

httpServer.go:此处只写做了修改的方法,其他没有改动的方法就不贴了,看着太乱

  • 实现httpServer.serve()方法:

// serve 查找路由树并执行命中的业务逻辑
func (s *HTTPServer) serve(ctx *Context) {
	method := ctx.Req.Method
	path := ctx.Req.URL.Path
	targetNode, ok := s.findRoute(method, path)
	// 没有在路由树中找到对应的路由节点 或 找到了路由节点的处理函数为空(即NPE:none pointer exception 的问题)
	// 则返回404
	if !ok || targetNode.HandleFunc == nil {
		ctx.Resp.WriteHeader(http.StatusNotFound)
		// 此处确实会报错 但是作为一个WEB框架 遇上了这种错误也没有特别好的处理办法
		// 最多只能是落个日志
		_, _ = ctx.Resp.Write([]byte("Not Found"))
		return
	}

	// 执行路由节点的处理函数
	targetNode.HandleFunc(*ctx)
}
  • 删除http. ServeHTTP()方法中的panic()

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 构建上下文
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	// 查找路由树并执行命中的业务逻辑
	s.serve(ctx)
}

PART2. 修改HandleFunc类型的入参

2.1 修改入参类型为*Context

之前入参类型为Context

handleFunc.go:

type HandleFunc func(ctx *Context)

2.2 修改其他受影响的地方

此处只列出受影响的方法或文件(文件基本上都是测试文件),读者自行比对修改即可

  • HTTPServer.serve()

  • httpServer_test.go

  • router_test.go

PART3. 测试

TODO:文件开头的那句go:build e2e的含义要去查

在httpServer_test.go新创建一个测试函数:

httpServer_test.go:

func TestServer_serve(t *testing.T) {
	s := NewHTTPServer()
	handleFunc := func(ctx *Context) {
		// 直接调用http.ResponseWriter的Write方法时 默认响应码为200
		ctx.Resp.Write([]byte("hello order detail"))
	}
	s.addRoute(http.MethodGet, "/order/detail", handleFunc)
	_ = s.Start(":8081")
}

其实这里还有一个问题:如果将s := NewHTTPServer()更改为s := &HTTPServer{},就会因为router为空而触发panic.

PART4. 修改HTTPServer的成员属性

将*router修改为router

修改原因:就是为了和PPT上的统一一下.这里我看过,GIN的Engine是组合了RouterGroup;而Beego的HttpServer则是组合了*ControllerRegister.倒不是大家都用非指针类型

  • httpServer.go:

// HTTPServer HTTP服务器
type HTTPServer struct {
	router
}
  • router.go:

// newRouter 创建路由森林
func newRouter() router {
	return router{
		trees: map[string]*node{},
	}
}
  • router_test.go中也有修改

至此,静态路由匹配功能完成.将此版本作为v2.

附录

需要课后尝试的内容

将HttpServer结构体修改为私有

这个修改的目的在于:强迫使用者必须使用NewHttpServer()函数来创建HttpServer结构体的实例.否则他自己直接s := &HTTPServer{}触发panic.

何时将成员属性类型定义为指针类型?何时将成员属性类型定义为结构体类型?

以HTTPServer为例:

type HTTPServer struct {
	*router
}

case1. 使用者用&HTTPServer的情况

我们期望使用者用HTTPServer的指针,因此其成员属性router是不是指针就不重要了,都行

这里我试过:

type HTTPServer struct {
	*router
}

s := &HTTPServer{}
if s.router == nil {
	fmt.Printf("s.router is nil\n")
}

则打印:s.router is nil

但如果改为:

type HTTPServer struct {
	router
}

s := &HTTPServer{}
fmt.Printf("%p\n", &s.router)

则打印:0xc00011a050.可以看到已经给router分配了内存

case2. 使用者用HTTPServer的情况

type HTTPServer struct {
	*router
}

s := HTTPServer{}
if s.router == nil {
	fmt.Printf("s.router is nil\n")
}

则打印:s.router is nil

type HTTPServer struct {
	router
}

s := HTTPServer{}
fmt.Printf("%p\n", &s.router)

则打印:0xc0000aa050.同样已经给router分配了内存.但是!这种方式要考虑值传递的问题.不要忘记GO语言中所有的传递都是值传递!

结论:拿不准就用指针!

Last updated 9 months ago

IDE调试功能