极客兔兔7天教程(从0到1)-web-学习笔记

极客兔兔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 )
    // w http.ResponseWriter
    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)
		// 第二个参数 实现 Handler 接口, 
        log.Fatal(http.ListenAndServe(":9999", engine))
    }

day1-03 http 请求抽离封装

  1. 定义一个方法 接收 http w, req
  2. 引擎封装,实现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 及任意数字转换
    // 转换器构造,指定要输出的 writer  
    encoder := json.NewEncoder(http.ResponseWriter)
	// 数据转换 obj = interface{}
	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{
		// make!!!
        handlers: make(map[string]HandlerFunc),
    }
}

day03 路由树封装及节点插入,匹配

路径拆分
  • string 转 切片
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"
    //sliceData := []string{"/","test","-","slice","-as-","map-key"}
    strAsSliceData := "/test-slice-as-map-key"
	// 直接字符串当作切片用,按索引使用
    fmt.Println(strAsSliceData[1:])//test-slice-as-map-key
    fmt.Println(mapData[strAsSliceData[1:]]) //test slice as map key
}
测试用例及反射断言的使用
  • 直接在要测试的包下,创建已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")
    }
}
  • 测试命令
#切换到 xxx_test.go 所在的目录
go test

day4-分组的实现及互相嵌套

  • go 是支持互相嵌套的
// Engine 实现 ServeHTTP 接口
type Engine struct {
	// 嵌套group
    *RouterGroup
    router *router
    groups []*RouterGroup // 储存所有分组
}

type RouterGroup struct {
    prefix     string
    middleware []HandlerFunc // 支持中间件
    parent     *RouterGroup  // 支持 next
	// 嵌套engine, 互相嵌套
    engine     *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)
}

// GET 定义一个方法 加入get 请求
func (group *RouterGroup) GET(pattern string, handlerFunc HandlerFunc) {
    group.addRoute("GET", pattern, handlerFunc)
}

// POST 定义一个方法 加入get 请求
func (group *RouterGroup) POST(pattern string, handlerFunc HandlerFunc) {
    group.addRoute("POST", pattern, handlerFunc)
}

// 1. addRoute 是group自己的方法, 
// 2. Post, Get 方法里 直接使用group调用, group.prefix = "/v1" 属性会保留
// 3. 链式调用 group.engine.RouterGroup.addRoute() 调用,自己本身的属性会被丢失

day5-中间件

中间件定义-定义一个func
  • demo
  • for 忽略初始化条件的调用
for;c.index<s ;c.index++{

}

day6- 模板

http 的 FileSystem , FsServer
  • 文件处理 http 已经实现 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.Template 将模板加载进内存
	// template.FuncMap 自定义渲染
    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[:]) // skip first 3 caller

	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 里面
  • 中间件流程梳理
  1. 初始化一个空的中间件 slice
  2. 扫描当前path是否有命中的路由组
  3. 将匹配到的所有路由组对应的中间件追加到初始化的中间件里面
  4. 将这个中间件 slice 赋值给 当前content.handlers
  5. 执行handler. 匹配当前path 对应的 HandlerFunc
  6. 将 匹配到的 HandlerFunc 在追加到 content.handlers
  7. 循环当前的content.handlers 开始执行 HandlerFunc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值