极客兔兔7天教程(从0到1)-web-学习笔记
原文 https://geektutu.com/post/gee.html
http 输入输出
http 响应写入 fmt.Fprintf()
Fprintf 根据格式说明符格式化并写入 w。它返回写入的字节数和遇到的任何写入错误。func Fprintf(w io . Writer , 格式字符串, a ... any ) (n int , err error )
fmt. Fprintf ( w, "URL.Path = %q\n" , req. URL. Path)
http listenAndServer 操作接口的实现
handler interface 的实现,将所有http 亲求转向我们自己的逻辑 定义 handler 本质是个接口,只需要实现 ServeHTTP 方法即可
type Engine struct { }
func ( engine * Engine) ServeHTTP ( w http. ResponseWriter, req * http. Request) {
switch req. URL. Path {
case "/" :
fmt. Fprintf ( w, "URL.Path = %q\n" , req. URL. Path)
case "/hello" :
for k, v := range req. Header {
fmt. Fprintf ( w, "Header[%q] = %q\n" , k, v)
}
default :
fmt. Fprintf ( w, "404 NOT FOUND %s" , req. URL)
}
}
func main ( ) {
engine := new ( Engine)
log. Fatal ( http. ListenAndServe ( ":9999" , engine) )
}
day1-03 http 请求抽离封装
定义一个方法 接收 http w, req 引擎封装,实现ServeHTTP& route 标识路径对应的 操作
type HandlerFunc func ( http. ResponseWriter, * http. Request)
type Engine struct {
router map [ string ] HandlerFunc
}
func ( engine * Engine) ServeHTTP ( w http. ResponseWriter, req * http. Request) {
key := req. Method + "-" + req. URL. Path
if handler, ok := engine. router[ key] ; ok {
handler ( w, req)
} else {
fmt. Fprintf ( w, "404 NOT FOUND: %s\n" , req. URL)
}
}
day02 http 响应全封装
json 的转换
json 转换构造实现 Writer interface 及任意数字转换
encoder := json. NewEncoder ( http. ResponseWriter)
err := encoder. Encode ( ojb)
http 的 错误code 输出 http.Error
http. Error ( http. ResponseWriter, err. Error ( ) , statusCode)
struct 定义的map 类型的属性,记得在实例化时 make
type router struct {
handlers map [ string ] HandlerFunc
}
func newRouter ( ) * router{
return & router{
handlers: make ( map [ string ] HandlerFunc) ,
}
}
day03 路由树封装及节点插入,匹配
路径拆分
vs := strings. Split ( pattern, "/" )
字符串拆分后记得使用单引号进行比较 切片转string
strings. Join ( [ ] string , "/" )
字符串当前切片用,截取 作为 map 类型的key
func strToSliceAsMapKey ( ) {
mapData := make ( map [ string ] string )
mapData[ "test-slice-as-map-key" ] = "test slice as map key"
strAsSliceData := "/test-slice-as-map-key"
fmt. Println ( strAsSliceData[ 1 : ] )
fmt. Println ( mapData[ strAsSliceData[ 1 : ] ] )
}
测试用例及反射断言的使用
直接在要测试的包下,创建已xxx_test.go 结尾的文件
# gee folder 目录下
router_test.go
定义TestXxx 开头的方法,及 testing 包的使用,以及反射比较的使用reflect.DeepEqual
t.Fatal
返回测试错误
func TestParsePattern ( t * testing. T) {
ok := reflect. DeepEqual ( parsePattern ( "/p/:name" ) , [ ] string { "p" , ":name" } )
ok = ok && reflect. DeepEqual ( parsePattern ( "/p/*" ) , [ ] string { "p" , "*" } )
ok = ok && reflect. DeepEqual ( parsePattern ( "/p/:name/*" ) , [ ] string { "p" , "*name" } )
if ! ok {
t. Fatal ( "test parsePattern failed" )
}
}
go test
day4-分组的实现及互相嵌套
type Engine struct {
* RouterGroup
router * router
groups [ ] * RouterGroup
}
type RouterGroup struct {
prefix string
middleware [ ] HandlerFunc
parent * RouterGroup
engine * Engine
}
互相嵌套,要用自己的实例链式调用继承类的方法,否则会导致自己当前的属性是默认值
func ( group * RouterGroup) addRoute ( method string , comp string , handlerFunc HandlerFunc) {
pattern := group. prefix + comp
log. Printf ( "Route %4s - %s" , method, pattern)
group. engine. router. addRoute ( method, pattern, handlerFunc)
}
func ( group * RouterGroup) GET ( pattern string , handlerFunc HandlerFunc) {
group. addRoute ( "GET" , pattern, handlerFunc)
}
func ( group * RouterGroup) POST ( pattern string , handlerFunc HandlerFunc) {
group. addRoute ( "POST" , pattern, handlerFunc)
}
day5-中间件
中间件定义-定义一个func
for ; c. index< s ; c. index++ {
}
day6- 模板
http 的 FileSystem , FsServer
func ( group * RouterGroup) createStaticHandler ( relativePath string , fs http. FileSystem) HandlerFunc {
absolutePath := path. Join ( group. prefix, relativePath)
fileServer := http. StripPrefix ( absolutePath, http. FileServer ( fs) )
return func ( c * Context) {
file := c. Param ( "filepath" )
if _ , err:= fs. Open ( file) ; err != nil {
c. Status ( http. StatusNotFound)
return
}
fileServer. ServeHTTP ( c. Writer, c. Req)
}
}
http.Dir path.Join text/template 和 html/template 2个标准库模板 为 HTML 提供了较为完整的支持 html 处理
template. Must ( template. New ( "" ) . Funcs ( engine. funcMap) . ParseGlob ( pattern) )
循环嵌套
A 嵌套B ,B 嵌套 A 先初始化 A, A 里面有方法初始化B A 调用 方法 初始化 B时,将 A 赋值给B
type A string {
B
}
type B string {
a A
}
func ( a * A) Run ( ) {
b:= & B{ }
b. a = a
}
func main ( ) {
a := & A{ }
a. Run ( )
}
day7 错误恢复
出现 panic 会终止程序, 但是在退出前,会先处理完当前协程上已经defer 的任务,执行完成后再退出。效果类似于 java 语言的 try…catch。 recover 函数只在defer中生效 切片会有访问索引不存在的错误,map 没有 recover 接收的是最终的错误字符串 错误跟踪,类似php 中exception的 getFile, getLine()
在 trace() 中,调用了 runtime.Callers(3, pcs[:]),Callers 用来返回调用栈的程序计数器, 第 0 个 Caller 是 Callers 本身,第 1 个是上一层 trace,第 2 个是再上一层的 defer func。因此,为了日志简洁一点,我们跳过了前 3 个 Caller。 通过 runtime.FuncForPC(pc) 获取对应的函数,在通过 fn.FileLine(pc) 获取到调用该函数的文件名和行号,打印在日志中。
func trace ( message string ) string {
var pcs [ 32 ] uintptr
n := runtime. Callers ( 3 , pcs[ : ] )
var str strings. Builder
str. WriteString ( message + "\nTraceback:" )
for _ , pc := range pcs[ : n] {
fn := runtime. FuncForPC ( pc)
file, line := fn. FileLine ( pc)
str. WriteString ( fmt. Sprintf ( "\n\t%s:%d" , file, line) )
}
return str. String ( )
}
处理流程分析
ServerHTTP 每次请求都会实例化一个 Content 初始化的engine 用来保存所有的 路由映射的 HandlerFunc 中间件独立放置,每个路由对应的 HandlerFunc 都会遍历,最终合并到middlewares 里面 中间件流程梳理
初始化一个空的中间件 slice 扫描当前path是否有命中的路由组 将匹配到的所有路由组对应的中间件追加到初始化的中间件里面 将这个中间件 slice 赋值给 当前content.handlers 执行handler. 匹配当前path 对应的 HandlerFunc 将 匹配到的 HandlerFunc 在追加到 content.handlers 循环当前的content.handlers 开始执行 HandlerFunc