[
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n.idea\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Ming Deng\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# toy-web\n用于极客时间go基础课程\n"
  },
  {
    "path": "demo/filters/filter.go",
    "content": "package filters\n\nimport (\n\t\"fmt\"\n\tweb \"geektime/toy-web/pkg\"\n)\n\nfunc init() {\n\tweb.RegisterFilter(\"my-custom\", myFilterBuilder)\n}\n\nfunc myFilterBuilder(next web.Filter) web.Filter {\n\treturn func(c *web.Context) {\n\t\tfmt.Println(\"假装这是我自定义的 filter\")\n\t\tnext(c)\n\t}\n}\n"
  },
  {
    "path": "demo/user.go",
    "content": "package demo\n\nimport (\n\t\"fmt\"\n\tweb \"geektime/toy-web/pkg\"\n\t\"time\"\n)\n\nfunc SignUp(c *web.Context) {\n\treq := &signUpReq{}\n\terr := c.ReadJson(req)\n\tif err != nil {\n\t\t_ = c.BadRequestJson(&commonResponse{\n\t\t\tBizCode: 4, // 假如说我们这个代表输入参数错误\n\t\t\t// 注意这里是demo，实际中你应该避免暴露 error\n\t\t\tMsg: fmt.Sprintf(\"invalid request: %v\", err),\n\t\t})\n\t\treturn\n\t}\n\t_ = c.OkJson(&commonResponse{\n\t\t// 假设这个是新用户的 ID\n\t\tData: 123,\n\t})\n}\n\nfunc SlowService(c *web.Context) {\n\ttime.Sleep(time.Second * 10)\n\t_ = c.OkJson(&commonResponse{\n\t\tMsg: \"Hi, this is msg from slow service\",\n\t})\n}\n\ntype signUpReq struct {\n\tEmail string `json:\"email\"`\n\tPassword string `json:\"password\"`\n\tConfirmedPassword string `json:\"confirmed_password\"`\n}\n\ntype commonResponse struct {\n\tBizCode int `json:\"biz_code\"`\n\tMsg string `json:\"msg\"`\n\tData interface{} `json:\"data\"`\n}"
  },
  {
    "path": "examples/first_lesson/afterclass/fibonacci.go",
    "content": "package main\n\nfunc main() {\n\n}\n\nfunc fibonacci(n int) int {\n\t// TODO\n\treturn 0\n}\n"
  },
  {
    "path": "examples/first_lesson/afterclass/fmt.go",
    "content": "package main\n\nfunc main() {\n\t\n}\n\n// 输出两位小数\nfunc printNumWith2(float642 float64) string {\n\treturn \"\"\n}\n\nfunc printBytes(data []byte) string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "examples/first_lesson/afterclass/slice.go",
    "content": "package main\n\nfunc main() {\n\ts := []int{1, 2, 4, 7}\n\t// 结果应该是 5, 1, 2, 4, 7\n\ts = Add(s, 0, 5)\n\n\t// 结果应该是5, 9, 1, 2, 4, 7\n\ts = Add(s, 1, 9)\n\n\t// 结果应该是5, 9, 1, 2, 4, 7, 13\n\ts = Add(s, 6, 13)\n\n\t// 结果应该是5, 9, 2, 4, 7, 13\n\ts = Delete(s, 2)\n\n\t// 结果应该是9, 2, 4, 7, 13\n\ts = Delete(s, 0)\n\n\t// 结果应该是9, 2, 4, 7\n\ts = Delete(s, 4)\n\n}\n\nfunc Add(s []int, index int, value int) []int {\n\t//TODO\n\treturn s\n}\n\nfunc Delete(s []int, index int) []int {\n\t// TODO\n\treturn s\n}\n"
  },
  {
    "path": "examples/first_lesson/array_slice/array.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\t// 直接初始化一个三个元素的数组。大括号里面多一个或者少一个都编译不通过\n\ta1 := [3]int{9, 8, 7}\n\tfmt.Printf(\"a1: %v, len: %d, cap: %d\", a1, len(a1), cap(a1))\n\n\t// 初始化一个三个元素的数组，所有元素都是0\n\tvar a2 [3]int\n\tfmt.Printf(\"a2: %v, len: %d, cap: %d\", a2, len(a2), cap(a2))\n\n\t//a1 = append(a1, 12) 数组不支持 append 操作\n\n\t// 按下标索引\n\tfmt.Printf(\"a1[1]: %d\", a1[1])\n\t// 超出下标范围，直接崩溃，编译不通过\n\t//fmt.Printf(\"a1[99]: %d\", a1[99])\n}"
  },
  {
    "path": "examples/first_lesson/array_slice/slice.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\ts1 := []int{1, 2, 3, 4} // 直接初始化了 4 个元素的切片\n\tfmt.Printf(\"s1: %v, len %d, cap: %d \\n\", s1, len(s1), cap(s1))\n\n\ts2 := make([]int, 3, 4) // 创建了一个包含三个元素，容量为4的切片\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\t// s2 目前 [0, 0, 0], append（追加）一个元素，变成什么？\n\ts2 = append(s2, 7) // 后边添加一个元素，没有超出容量限制，不会发生扩容\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\ts2 = append(s2, 8) // 后边添加了一个元素，触发扩容\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\ts3 := make([]int, 4) // 只传入一个参数，表示创建一个含有四个元素，容量也为四个元素的\n\t// 等价于 s3 := make([]int, 4, 4)\n\tfmt.Printf(\"s3: %v, len %d, cap: %d \\n\", s3, len(s3), cap(s3))\n\n\t// 按下标索引\n\tfmt.Printf(\"s3[2]: %d\", s3[2])\n\t// 超出下标范围，直接崩溃\n\t// runtime error: index out of range [99] with length 4\n\t// fmt.Printf(\"s3[99]: %d\", s3[99])\n\n\t// SubSlice()\n\n\t//shareArr()\n}\n\nfunc SubSlice() {\n\ts1 := []int{2, 4, 6, 8, 10}\n\ts2 := s1[1:3]\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\ts3 := s1[2:]\n\tfmt.Printf(\"s3: %v, len %d, cap: %d \\n\", s3, len(s3), cap(s3))\n\n\ts4 := s1[:3]\n\tfmt.Printf(\"s4: %v, len %d, cap: %d \\n\", s4, len(s4), cap(s4))\n}\n\nfunc ShareSlice() {\n\n\ts1 := []int{1, 2, 3, 4}\n\ts2 := s1[2:]\n\tfmt.Printf(\"s1: %v, len %d, cap: %d \\n\", s1, len(s1), cap(s1))\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\ts2[0] = 99\n\tfmt.Printf(\"s1: %v, len %d, cap: %d \\n\", s1, len(s1), cap(s1))\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\ts2 = append(s2, 199)\n\tfmt.Printf(\"s1: %v, len %d, cap: %d \\n\", s1, len(s1), cap(s1))\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n\n\ts2[1] = 1999\n\tfmt.Printf(\"s1: %v, len %d, cap: %d \\n\", s1, len(s1), cap(s1))\n\tfmt.Printf(\"s2: %v, len %d, cap: %d \\n\", s2, len(s2), cap(s2))\n}\n"
  },
  {
    "path": "examples/first_lesson/fmt/fmt.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tname:=\"Tom\"\n\tage := 17\n\t// 这个 API 是返回字符串的，所以大多数时候我们都是用这个\n\tstr := fmt.Sprintf(\"hello, I am %s, I am %d years old \\n\", name, age)\n\tprintln(str)\n\n\t// 这个是直接输出，一般简单程序 DEBUG 会用它输出到一些信息到控制台\n\tfmt.Printf(\"hello, I am %s, I am %d years old \\n\", name, age)\n\n\treplaceHolder()\n}\n\nfunc replaceHolder() {\n\tu := &user{\n\t\tName: \"Tom\",\n\t\tAge: 17,\n\t}\n\tfmt.Printf(\"v => %v \\n\", u)\n\tfmt.Printf(\"+v => %+v \\n\", u)\n\tfmt.Printf(\"#v => %#v \\n\", u)\n\tfmt.Printf(\"T => %T \\n\", u)\n}\n\ntype user struct {\n\tName string\n\tAge int\n}\n"
  },
  {
    "path": "examples/first_lesson/for/for.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tForLoop()\n\tForI()\n\tForR()\n}\n\nfunc ForLoop()  {\n\tarr := []int {9, 8, 7, 6}\n\tindex := 0\n\tfor {\n\t\tif index == 3{\n\t\t\t// break 跳出循环\n\t\t\tbreak\n\t\t}\n\t\tfmt.Printf(\"%d => %d\\n\", index, arr[index])\n\t\tindex ++\n\t}\n\tfmt.Println(\" for loop end \\n \")\n}\n\nfunc ForI()  {\n\tarr := []int {9, 8, 7, 6}\n\tfor i := 0; i < len(arr); i++ {\n\t\tfmt.Printf(\"%d => %d \\n\", i, arr[i])\n\t}\n\tfmt.Println(\"for i loop end \\n \")\n}\n\nfunc ForR()  {\n\tarr := []int {9, 8, 7, 6}\n\n\tfor index, value := range arr {\n\t\tfmt.Printf(\"%d => %d\\n\", index, value)\n\t}\n\n\t// 如果只是需要 value, 可以用 _ 代替 index\n\tfor _, value := range arr {\n\t\tfmt.Printf(\"only value: %d \\n\", value)\n\t}\n\n\t// 如果只需要 index 也可以去掉 写成 for index := range arr\n\tfor index := range arr {\n\t\tfmt.Printf(\"only index: %d \\n\", index)\n\t}\n\n\tfmt.Println(\"for r loop end \\n \")\n}\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "examples/first_lesson/func_dec/funcs.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\ta := Fun0(\"Tom\")\n\tprintln(a)\n\n\tb, c := Fun1(\"a\", 17)\n\tprintln(b)\n\tprintln(c)\n\n\t_, d := Fun2(\"a\", \"b\")\n\tprintln(d)\n\n\t// 不定参数后面可以传递任意多个值\n\tFun4(\"hello\", 19, \"CUICUI\", \"DaMing\")\n\ts := []string{\"CUICUI\", \"DaMing\"}\n\tFun4(\"hello\", 19, s...)\n}\n\n// Fun0 只有一个返回值，不需要括号括起来\nfunc Fun0(name string) string {\n\treturn \"Hello, \" + name\n}\n\n// Fun1 多个参数，多个返回值。参数有名字，但是返回值没有\nfunc Fun1(a string, b int) (int, string) {\n\treturn 0, \"你好\"\n}\n\n// Fun2 的返回值具有名字，可以在内部直接复制，然后返回\n// 也可以忽略age, name，直接返回别的。\nfunc Fun2(a string, b string) (age int, name string) {\n\tage = 19\n\tname = \"Tom\"\n\treturn\n\t//return 19, \"Tom\" // 这样返回也可以\n}\n\n// Fun3 多个参数具有相同类型放在一起，可以只写一次类型\nfunc Fun3(a, b, c string, abc, bcd int, p string) (d, e int, g string) {\n\td = 15\n\te = 16\n\tg = \"你好\"\n\treturn\n\t//return 0, 0, \"你好\" // 这样也可以\n}\n\n// Fun4 不定参数。不定参数要放在最后面\nfunc Fun4(a string, b int, names...string)  {\n\t// 我们使用的时候可以直接把 names 看做切片\n\tfor _, name := range names {\n\t\tfmt.Printf(\"不定参数：%s \\n\", name)\n\t}\n}"
  },
  {
    "path": "examples/first_lesson/if_else/ifelse.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tYoung(9)\n\tYoung(100)\n\n\tIfUsingNewVariable(10, 200)\n\tIfUsingNewVariable(100, 30)\n}\n\nfunc Young(age int) {\n\tif age < 18{\n\t\tfmt.Println(\"I am a child!\")\n\t} else {\n\t\t// else 分支也可以没有\n\t\tfmt.Println(\"I not a child\")\n\t}\n}\n\nfunc IfUsingNewVariable(start int, end int) {\n\tif distance := end - start; distance > 100 {\n\t\tfmt.Printf(\"距离太远，不来了： %d\\n\", distance)\n\t} else {\n\t\t// else 分支也可以没有\n\t\tfmt.Printf(\"距离并不远，来一趟： %d\\n\", distance)\n\t}\n\n\t// 这里不能访问  distance\n\t//fmt.Printf(\"距离是： %d\\n\", distance)\n}"
  },
  {
    "path": "examples/first_lesson/package_dec/multi_same/a.go",
    "content": "package multi_same\n"
  },
  {
    "path": "examples/first_lesson/package_dec/multi_same/b.go",
    "content": "package multi_same\n"
  },
  {
    "path": "examples/first_lesson/package_dec/not_same/not_same.go",
    "content": "package not_same_aaaa\n"
  },
  {
    "path": "examples/first_lesson/switch/switch.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tChooseFruit(\"蓝莓\")\n\tChooseFruit(\"苹果\")\n\tChooseFruit(\"西瓜\")\n}\n\nfunc ChooseFruit(fruit string) {\n\tswitch fruit {\n\tcase \"苹果\":\n\t\tfmt.Println(\"这是一个苹果\")\n\tcase \"草莓\", \"蓝莓\":\n\t\tfmt.Println(\"这是霉霉\")\n\tdefault:\n\t\tfmt.Printf(\"不知道是啥：%s \\n\", fruit)\n\t}\n}\n"
  },
  {
    "path": "examples/first_lesson/types/rune.go",
    "content": "package main\n\nfunc main() {\n\tvar a byte = 13\n}\n"
  },
  {
    "path": "examples/first_lesson/types/string.go",
    "content": "package main\n\nimport \"unicode/utf8\"\n\nfunc main() {\n\t// 一般推荐用于短的，不用换行的，不含双引号的\n\tprintln(\"He said:\\\" Hello Go \\\" \")\n\t// 长的，复杂的。比如说放个 json 串\n\tprintln(`He said: \"hello, Go\"\n我还可以换个行\n`)\n\n\n\tprintln(len(\"你好\")) // 输出6\n\tprintln(utf8.RuneCountInString(\"你好\")) // 输出 2\n\tprintln(utf8.RuneCountInString(\"你好ab\")) // 输出 4\n\n\t// 反正遇到计算字符个数，比如说用户名字多长，博客多长这种字符个数\n\t// 记得用 utf8.RuneCountInString\n\n\t// 字符串拼接。只能发生在 string 之间\n\tprintln(\"Hello, \" + \"Go!\")\n\n}\n"
  },
  {
    "path": "examples/first_lesson/var_and_const/assignment.go",
    "content": "package main\n\nfunc main() {\n\t a := 13\n\t println(a)\n\t b := \"你好\"\n\t println(b)\n}\n\n"
  },
  {
    "path": "examples/first_lesson/var_and_const/const.go",
    "content": "package main\n\nconst internal = \"包内可访问\"\nconst External = \"包外可访问\"\n\nfunc main() {\n\tconst a = \"你好\"\n\tprintln(a)\n}\n"
  },
  {
    "path": "examples/first_lesson/var_and_const/var.go",
    "content": "package main\n\n// Global 首字母大写，全局可以访问\nvar Global = \"全局变量\"\n\n// 首字母小写，只能在这个包里面使用\n// 其子包也不能用\nvar local = \"包变量\"\n\nvar (\n\tFirst string = \"abc\"\n\tsecond int32 = 16\n)\n\nfunc main() {\n\t// int 是灰色的，是因为 golang 自己可以做类型推断，它觉得你可以省略\n\tvar a int = 13\n\tprintln(a)\n\n\t// 这里我们省略了类型\n\tvar b = 14\n\tprintln(b)\n\n\t// 这里 uint 不可省略，因为生路之后，因为不加 uint 类型，15会被解释为 int 类型\n\tvar c uint = 15\n\tprintln(c)\n\n\t// 这一句无法通过编译，因为 golang 是强类型语言，并且不会帮你做任何的转换\n\t// println(a == c)\n\n\t// 只声明不赋值，d 是默认值 0，类型不可以省略\n\tvar d int\n\tprintln(d)\n}\n\n\n\n"
  },
  {
    "path": "examples/first_lesson/var_and_const/var_wrong.go",
    "content": "package main\n\nvar aa = \"hello\"\n// var aa = \"bbb\" 这个包已经有一个 a 了，所以再次声明会导致编译\nfunc main() {\n\taa := 13 // 虽然包外面已经有一个 aa 了，但是这里从包变成了局部变量\n\tprintln(aa)\n\n\tvar bb = 15\n\t//var bb = 16 // 重复声明，也会导致编译不通过\n\tprintln(bb)\n\n\tbb = 17 // OK,没有重复声明，只是赋值了新的值\n\t// bb := 18 // 不行，因为 := 就是声明并且赋值的简写，相当于重复声明了 bb\n}\n"
  },
  {
    "path": "examples/forth_lesson/atomic/atomic.go",
    "content": "package main\n\nimport \"sync/atomic\"\n\nvar value int32 = 0\nfunc main() {\n\t// 要传入 value 的指针\n\t// 把 value + 10\n\tatomic.AddInt32(&value, 10)\n\tnv := atomic.LoadInt32(&value)\n\t// 输出10\n\tprintln(nv)\n\t// 如果之前的值是10，那么就设置为新的值 20\n\tswapped := atomic.CompareAndSwapInt32(&value, 10, 20)\n\t// 输出 true\n\tprintln(swapped)\n\n\t// 如果之前的值是19，那么就设置为新的值 50\n\t// 显然现在 value 是 20\n\tswapped = atomic.CompareAndSwapInt32(&value, 19, 50)\n\t// 输出 false\n\tprintln(swapped)\n\n\told := atomic.SwapInt32(&value, 40)\n\t// 应该是20，即原本的值\n\tprintln(old)\n\t// 输出新的值，也就是交换后的值，40\n\tprintln(value)\n}"
  },
  {
    "path": "examples/forth_lesson/channel/channel.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc main() {\n\tchannelWithoutCache()\n\tchannelWithCache()\n}\n\nfunc channelWithCache()  {\n\tch := make(chan string, 1)\n\tgo func() {\n\n\t\tch <- \"Hello, first msg from channel\"\n\t\ttime.Sleep(time.Second)\n\t\tch <- \"Hello, second msg from channel\"\n\t}()\n\n\ttime.Sleep(2 * time.Second)\n\tmsg := <- ch\n\tfmt.Println(time.Now().String() + msg)\n\tmsg = <- ch\n\tfmt.Println(time.Now().String() + msg)\n\t// 因为前面我们先睡了2秒，所以其实会有一个已经在缓冲了\n\t// 当我们尝试输出的时候，这个输出间隔就会明显小于1秒\n\t// 我电脑上的几次实验，差距都在1ms以内\n}\n\nfunc channelWithoutCache() {\n\t// 不带缓冲\n\tch := make(chan string)\n\tgo func() {\n\t\ttime.Sleep(time.Second)\n\t\tch <- \"Hello, msg from channel\"\n\t}()\n\n\t// 这里比较容易写成 msg <- ch，编译会报错\n\tmsg := <- ch\n\tfmt.Println(msg)\n}\n"
  },
  {
    "path": "examples/forth_lesson/context/context.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc main() {\n\tWithTimeout()\n\tWithCancel()\n\tWithDeadline()\n\tWithValue()\n}\n\nfunc WithTimeout() {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second * 2)\n\tdefer cancel()\n\n\tstart := time.Now().Unix()\n\t<- ctx.Done()\n\tend := time.Now().Unix()\n\t// 输出2，说明在 ctx.Done()这里阻塞了两秒\n\tfmt.Println(end-start)\n}\n\nfunc WithCancel() {\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() {\n\t\t<- ctx.Done()\n\t\tfmt.Println(\"context was canceled\")\n\t}()\n\t// 确保我们的 goroutine进去执行了\n\ttime.Sleep(time.Second)\n\tcancel()\n\t// 确保后面那句打印出来了\n\ttime.Sleep(time.Second)\n}\n\nfunc WithDeadline() {\n\t// 设置两秒后超时\n\tctx, cancel := context.WithDeadline(context.Background(),\n\t\ttime.Now().Add(2 * time.Second))\n\tdefer cancel()\n\n\tstart := time.Now().Unix()\n\t<- ctx.Done()\n\tend := time.Now().Unix()\n\t// 输出2，说明在 ctx.Done()这里阻塞了两秒\n\tfmt.Println(end-start)\n}\n\nfunc WithValue() {\n\tparentKey := \"parent\"\n\tparent := context.WithValue(context.Background(), parentKey, \"this is parent\")\n\n\tsonKey := \"son\"\n\tson := context.WithValue(parent, sonKey, \"this is son\")\n\n\t// 尝试从 parent 里面拿出来 key = son的，会拿不到\n\tif parent.Value(parentKey) == nil {\n\t\tfmt.Printf(\"parent can not get son's key-value pair\")\n\t}\n\n\tif val := son.Value(parentKey); val != nil {\n\t\tfmt.Printf(\"parent can not get son's key-value pair\")\n\t}\n}\n"
  },
  {
    "path": "examples/forth_lesson/init/init_order.go",
    "content": "package main\n\nfunc init() {\n\t// 因为我们不能确定 init 方法的执行顺序，\n\t// 只能曲线救国\n\tinitBeforeSomething()\n\tinitSomething()\n\tinitAfterSomething()\n}\n\nfunc initBeforeSomething()  {\n\t\n}\n\nfunc initSomething()  {\n\t\n}\n\nfunc initAfterSomething()  {\n\t\n}"
  },
  {
    "path": "examples/forth_lesson/init/multi_init.go",
    "content": "package main\n\nfunc init() {\n\t// 第一个\n}\n\nfunc init() {\n\t// 第二个\n}\n"
  },
  {
    "path": "examples/forth_lesson/select/select.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc main() {\n\t// 这个不能在 main 函数运行，是因为运行起来，\n\t// 所有的goroutine都被我们搞sleep了，直接就崩了\n\t//Select()\n}\n\nfunc Select() {\n\tch1 := make(chan string)\n\tch2 := make(chan string)\n\n\tgo func() {\n\t\ttime.Sleep(time.Second)\n\t\tch1 <- \"msg from channel1\"\n\t}()\n\n\tgo func() {\n\t\ttime.Sleep(time.Second)\n\t\tch2 <- \"msg from channel2\"\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase msg := <- ch1:\n\t\t\tfmt.Println(msg)\n\t\tcase msg := <- ch2:\n\t\t\tfmt.Println(msg)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/forth_lesson/static_resource/file_server.go",
    "content": "package main\n\nimport \"net/http\"\n\nfunc main() {\n\tserve := http.FileServer(http.Dir(\".\"))\n\t//http.Handle(\"/\", serve)\n\thttp.ListenAndServe(\":8080\", serve)\n}\n"
  },
  {
    "path": "examples/second_lesson/afterclass/set.go",
    "content": "package afterclass\n\ntype Set interface {\n\tPut(key string)\n\tKeys() []string\n\tContains(key string) bool\n\tRemove(key string)\n\t// 如果之前已经有了，就返回旧的值，absent =false\n\t// 如果之前没有，就塞下去，返回 absent = true\n\tPutIfAbsent(key string) (old string, absent bool)\n}\n"
  },
  {
    "path": "examples/second_lesson/afterclass/tree.go",
    "content": "package afterclass\n\ntype Tree interface {\n\n}\n\n// 二叉树\ntype binaryTree struct {\n\n}\n\n// 多叉树\ntype mutliWayTree struct {\n\n}\n"
  },
  {
    "path": "examples/second_lesson/composition/composition.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\n}\n\n// Swimming 会游泳的\ntype Swimming interface {\n\tSwim()\n}\n\ntype Duck interface {\n\t// 鸭子是会游泳的，所以这里组合了它\n\tSwimming\n}\n\n\ntype Base struct {\n\tName string\n}\n\ntype Concrete1 struct {\n\tBase\n}\n\ntype Concrete2 struct {\n\t*Base\n}\n\nfunc (c Concrete1) SayHello() {\n\t// c.Name 直接访问了Base的Name字段\n\tfmt.Printf(\"I am base and my name is: %s \\n\", c.Name)\n\t// 这样也是可以的\n\tfmt.Printf(\"I am base and my name is: %s \\n\", c.Base.Name)\n\n\t// 调用了被组合的\n\tc.Base.SayHello()\n}\n\nfunc (b *Base) SayHello() {\n\tfmt.Printf(\"I am base and my name is: %s \\n\", b.Name)\n}"
  },
  {
    "path": "examples/second_lesson/composition/no_over_write.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tson := Son{\n\t\tParent{},\n\t}\n\n\tson.SayHello()\n}\n\ntype Parent struct {\n\n}\n\nfunc (p Parent) SayHello() {\n\tfmt.Println(\"I am \" + p.Name())\n}\n\nfunc (p Parent) Name() string {\n\treturn \"Parent\"\n}\n\ntype Son struct {\n\tParent\n}\n\n// 定义了自己的 Name() 方法\nfunc (s Son) Name() string {\n\treturn \"Son\"\n}\n\n"
  },
  {
    "path": "examples/second_lesson/http/request_body.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc home(w http.ResponseWriter, r *http.Request)  {\n\tfmt.Fprint(w, \"Hi, this is home page\")\n}\n\nfunc readBodyOnce(w http.ResponseWriter, r *http.Request)  {\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tfmt.Fprintf(w, \"read body failed: %v\", err)\n\t\t// 记住要返回，不然就还会执行后面的代码\n\t\treturn\n\t}\n\t// 类型转换，将 []byte 转换为 string\n\tfmt.Fprintf(w, \"read the data: %s \\n\", string(body))\n\n\t// 尝试再次读取，啥也读不到，但是也不会报错\n\tbody, err = io.ReadAll(r.Body)\n\tif err != nil {\n\t\t// 不会进来这里\n\t\tfmt.Fprintf(w, \"read the data one more time got error: %v\", err)\n\t\treturn\n\t}\n\tfmt.Fprintf(w, \"read the data one more time: [%s] and read data length %d \\n\", string(body), len(body))\n}\n\n\nfunc getBodyIsNil(w http.ResponseWriter, r *http.Request) {\n\tif r.GetBody == nil {\n\t\tfmt.Fprint(w, \"GetBody is nil \\n\")\n\t} else {\n\t\tfmt.Fprintf(w, \"GetBody not nil \\n\")\n\t}\n}\n\nfunc queryParams(w http.ResponseWriter, r *http.Request) {\n\tvalues := r.URL.Query()\n\tfmt.Fprintf(w, \"query is %v\\n\", values)\n}\n\nfunc wholeUrl(w http.ResponseWriter, r *http.Request)  {\n\tdata, _ := json.Marshal(r.URL)\n\tfmt.Fprintf(w, string(data))\n}\n\nfunc header(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"header is %v\\n\", r.Header)\n}\n\nfunc form(w http.ResponseWriter, r *http.Request)  {\n\tfmt.Fprintf(w, \"before parse form %v\\n\", r.Form)\n\terr := r.ParseForm()\n\tif err != nil {\n\t\tfmt.Fprintf(w, \"parse form error %v\\n\", r.Form)\n\t}\n\tfmt.Fprintf(w, \"before parse form %v\\n\", r.Form)\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/\", home)\n\thttp.HandleFunc(\"/body/once\", readBodyOnce)\n\thttp.HandleFunc(\"/body/multi\", getBodyIsNil)\n\thttp.HandleFunc(\"/url/query\", queryParams)\n\thttp.HandleFunc(\"/header\", header)\n\thttp.HandleFunc(\"/wholeUrl\", wholeUrl)\n\thttp.HandleFunc(\"/form\", form)\n\tif err := http.ListenAndServe(\":8080\", nil); err != nil {\n\t\tpanic(err)\n\t}\n}"
  },
  {
    "path": "examples/second_lesson/map/map.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\t// 创建了一个预估容量是2的 map\n\tm := make(map[string]string, 2)\n\t// 没有指定预估容量\n\tm1 := make(map[string]string)\n\t// 直接初始化\n\tm2 := map[string]string{\n\t\t\"Tom\": \"Jerry\",\n\t}\n\n\t// 赋值\n\tm[\"hello\"] = \"world\"\n\tm1[\"hello\"] = \"world\"\n\t// 赋值\n\tm2[\"hello\"] = \"world\"\n\t// 取值\n\tval := m[\"hello\"]\n\tprintln(val)\n\n\t// 再次取值，使用两个返回值，后面的ok会告诉你map有没有这个key\n\tval, ok := m[\"invalid_key\"]\n\tif !ok {\n\t\tprintln(\"key not found\")\n\t}\n\n\tfor key, val := range m {\n\t\tfmt.Printf(\"%s => %s \\n\", key, val)\n\t}\n}"
  },
  {
    "path": "examples/second_lesson/server_context/signup.go",
    "content": "package server_context\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"geektime/toy-web/pkg/v2\"\n\t\"io\"\n\t\"net/http\"\n)\n\n// 在没有 context 抽象的情况下，是长这样的\nfunc SignUpWithoutContext(w http.ResponseWriter, r *http.Request) {\n\treq := &signUpReq{}\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tfmt.Fprintf(w, \"read body failed: %v\", err)\n\t\t// 要返回掉，不然就会继续执行后面的代码\n\t\treturn\n\t}\n\terr = json.Unmarshal(body, req)\n\tif err != nil {\n\t\tfmt.Fprintf(w, \"deserialized failed: %v\", err)\n\t\t// 要返回掉，不然就会继续执行后面的代码\n\t\treturn\n\t}\n\n\t// 返回一个虚拟的 user id 表示注册成功了\n\tfmt.Fprintf(w, \"%d\", err)\n}\n\nfunc SignUpWithoutWrite(w http.ResponseWriter, r *http.Request) {\n\tc := webv2.NewContext(w, r)\n\treq := &signUpReq{}\n\terr := c.ReadJson(req)\n\tif err != nil {\n\t\tresp := &commonResponse{\n\t\t\tBizCode: 4, // 假如说我们这个代表输入参数错误\n\t\t\tMsg: fmt.Sprintf(\"invalid request: %v\", err),\n\t\t}\n\t\trespBytes, _ := json.Marshal(resp)\n\t\tfmt.Fprint(w, string(respBytes))\n\t\treturn\n\t}\n\t// 这里又得来一遍 resp 转json\n\tfmt.Fprintf(w, \"invalid request: %v\", err)\n}\n\ntype signUpReq struct {\n\tEmail string `json:\"email\"`\n\tPassword string `json:\"password\"`\n\tConfirmedPassword string `json:\"confirmed_password\"`\n}\n\ntype commonResponse struct {\n\tBizCode int `json:\"biz_code\"`\n\tMsg string `json:\"msg\"`\n\tData interface{} `json:\"data\"`\n}\n"
  },
  {
    "path": "examples/second_lesson/struct/intf.go",
    "content": "package main\n\n// 首字母小写，所以是一个包私有的接口\ntype animal interface {\n\t// 这里可以有任意多个方法，不过我们一般建议是小接口，\n\t// 即接口里面不会有很多方法\n\t// 方法声明不需要 func 关键字\n\n\tEat()\n}\n\n// 首字母大写，所以是一个包外可访问的接口\ntype Duck interface {\n\tSwim()\n}\n"
  },
  {
    "path": "examples/second_lesson/struct/pointer.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\t// 指针用 * 表示\n\tvar p *ToyDuck = &ToyDuck{}\n\t// 解引用，得到结构体\n\tvar duck ToyDuck = *p\n\tduck.Swim()\n\n\t// 只是声明了，但是没有使用\n\tvar nilDuck *ToyDuck\n\tif nilDuck == nil {\n\t\tfmt.Println(\"nilDuck is nil\")\n\t}\n}"
  },
  {
    "path": "examples/second_lesson/struct/receiver.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\n\t// 因为 u 是结构体，所以方法调用的时候它数据是不会变的\n\tu := User{\n\t\tName: \"Tom\",\n\t\tAge: 10,\n\t}\n\tu.ChangeName(\"Tom Changed!\")\n\tu.ChangeAge(100)\n\tfmt.Printf(\"%v \\n\", u)\n\n\t// 因为 up 指针，所以内部的数据是可以被改变的\n\tup := &User{\n\t\tName: \"Jerry\",\n\t\tAge: 12,\n\t}\n\n\t// 因为 ChangeName 的接收器是结构体\n\t// 所以 up 的数据还是不会变\n\tup.ChangeName(\"Jerry Changed!\")\n\tup.ChangeAge(120)\n\n\tfmt.Printf(\"%v \\n\", up)\n}\n\ntype User struct {\n\tName string\n\tAge int\n}\n\n// 结构体接收器\nfunc (u User) ChangeName(newName string)  {\n\tu.Name = newName\n}\n\n// 指针接收器\nfunc (u *User) ChangeAge(newAge int) {\n\tu.Age = newAge\n}\n"
  },
  {
    "path": "examples/second_lesson/struct/self_ref.go",
    "content": "package main\n\nfunc main() {\n\n}\n\ntype Node struct {\n\t//自引用只能使用指针\n\t//left Node\n\t//right Node\n\n\tleft *Node\n\tright *Node\n\n\t// 这个也会报错\n\t// nn NodeNode\n}\n\n\ntype NodeNode struct {\n\tnode Node\n}"
  },
  {
    "path": "examples/second_lesson/struct/struct.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\t// duck1 是 *ToyDuck\n\tduck1 := &ToyDuck{}\n\tduck1.Swim()\n\n\tduck2 := ToyDuck{}\n\tduck2.Swim()\n\n\t// duck3 是 *ToyDuck\n\tduck3 := new(ToyDuck)\n\tduck3.Swim()\n\n\t// 当你声明这样的时候，Go 就帮你分配好内存\n\t// 不用担心空指针的问题，以为它压根就不是指针\n\tvar duck4 ToyDuck\n\tduck4.Swim()\n\n\t// duck5 就是一个指针了\n\tvar duck5 *ToyDuck\n\t// 这边会直接panic 掉\n\tduck5.Swim()\n\n\t// 赋值，初始化按字段名字赋值\n\tduck6 := ToyDuck{\n\t\tColor: \"黄色\",\n\t\tPrice: 100,\n\t}\n\tduck6.Swim()\n\n\t// 初始化按字段顺序赋值，不建议使用\n\tduck7 := ToyDuck{\"蓝色\", 1024}\n\tduck7.Swim()\n\n\t// 后面再单独赋值\n\tduck8 := ToyDuck{}\n\tduck8.Color = \"橘色\"\n\n}\n\n// ToyDuck 玩具鸭\ntype ToyDuck struct {\n\tColor string\n\tPrice uint64\n}\n\nfunc (t *ToyDuck) Swim() {\n\tfmt.Printf(\"门前一条河，游过一群鸭，我是%s，%d一只\\n\", t.Color, t.Price)\n}\n\n\n"
  },
  {
    "path": "examples/second_lesson/struct/type_a_b.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfake := FakeFish{}\n\t// fake 无法调用原来 Fish 的方法\n\t// 这一句会编译错误\n\t//fake.Swim()\n\tfake.FakeSwim()\n\n\t// 转换为Fish\n\ttd := Fish(fake)\n\t// 真的变成了鱼\n\ttd.Swim()\n\n\tsFake := StrongFakeFish{}\n\t// 这里就是调用了自己的方法\n\tsFake.Swim()\n\n\ttd = Fish(sFake)\n\t// 真的变成了鱼\n\ttd.Swim()\n}\n\n// 定义了一个新类型，注意是新类型\ntype FakeFish Fish\n\nfunc (f FakeFish) FakeSwim() {\n\tfmt.Printf(\"我是山寨鱼，嘎嘎嘎\\n\")\n}\n\n// 定义了一个新类型\ntype StrongFakeFish Fish\n\nfunc (f StrongFakeFish) Swim() {\n\tfmt.Printf(\"我是华强北山寨鱼，嘎嘎嘎\\n\")\n}\n\ntype Fish struct {\n}\n\nfunc (f Fish) Swim() {\n\tfmt.Printf(\"我是鱼，假装自己是一直鸭子\\n\")\n}\n"
  },
  {
    "path": "examples/second_lesson/struct/type_a_et_b.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tvar n News = fakeNews{\n\t\tName: \"hello\",\n\t}\n\tn.Report()\n}\n\ntype News struct {\n\tName string\n}\n\nfunc (d News) Report() {\n\tfmt.Println(\"I am news: \" + d.Name)\n}\n\ntype fakeNews = News"
  },
  {
    "path": "examples/third_lesson/closure/closure.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc main() {\n\n\ti := 13\n\ta := func() {\n\t\tfmt.Printf(\"i is %d \\n\", i)\n\t}\n\ta()\n\n\tfmt.Println(ReturnClosure(\"Tom\")())\n\n\tDelay()\n\ttime.Sleep(time.Second)\n}\n\nfunc ReturnClosure(name string) func() string {\n\treturn func() string {\n\t\treturn \"Hello, \" + name\n\t}\n}\n\nfunc Delay() {\n\tfns := make([]func(), 0, 10)\n\tfor i := 0; i < 10; i++ {\n\t\tfns = append(fns, func() {\n\t\t\tfmt.Printf(\"hello, this is : %d \\n\", i)\n\t\t})\n\t}\n\n\tfor _, fn := range fns {\n\t\tfn()\n\t}\n}\n"
  },
  {
    "path": "examples/third_lesson/defer/defer.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tdefer func() {\n\t\tfmt.Println(\"aaa\")\n\t}()\n\n\tdefer func() {\n\t\tfmt.Println(\"bbb\")\n\t}()\n\n\tdefer func() {\n\t\tfmt.Println(\"ccc\")\n\t}()\n}\n"
  },
  {
    "path": "examples/third_lesson/errors/error.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc main() {\n\tvar err error = &MyError{}\n\tprintln(err.Error())\n\n\tErrorsPkg()\n}\n\ntype MyError struct {\n}\n\nfunc (m *MyError) Error() string {\n\treturn \"Hello, it's my error\"\n}\n\nfunc ErrorsPkg()  {\n\terr := &MyError{}\n\t// 使用 %w 占位符，返回的是一个新错误\n\t// wrappedErr 是一个新类型，fmt.wrapError\n\twrappedErr := fmt.Errorf(\"this is an wrapped error %w\", err)\n\n\t// 再解出来\n\tif err == errors.Unwrap(wrappedErr) {\n\t\tfmt.Println(\"unwrapped\")\n\t}\n\t\n\tif errors.Is(wrappedErr, err) {\n\t\t// 虽然被包了一下，但是 Is 会逐层解除包装，判断是不是该错误\n\t\tfmt.Println(\"wrapped is err\")\n\t}\n\n\tcopyErr := &MyError{}\n\t// 这里尝试将 wrappedErr转换为 MyError\n\t// 注意我们使用了两次的取地址符号\n\tif errors.As(wrappedErr, &copyErr) {\n\t\tfmt.Println(\"convert error\")\n\t}\n}\n\n"
  },
  {
    "path": "examples/third_lesson/errors/panic.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tdefer func() {\n\t\tif data := recover(); data != nil {\n\t\t\tfmt.Printf(\"hello, panic: %v\\n\", data)\n\t\t}\n\t\tfmt.Println(\"恢复之后从这里继续执行\")\n\t}()\n\n\tpanic(\"Boom\")\n\tfmt.Println(\"这里将不会执行下来\")\n}\n"
  },
  {
    "path": "examples/third_lesson/goroutine/goroutine.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc main() {\n\tGoRoutine()\n}\n\nfunc GoRoutine() {\n\tgo func() {\n\t\ttime.Sleep(10 * time.Second)\n\t}()\n\t// 这里直接输出，不会等待十秒\n\tfmt.Println(\"I am here\")\n}"
  },
  {
    "path": "examples/third_lesson/sync/map.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\nfunc main() {\n\tm := sync.Map{}\n\tm.Store(\"cat\", \"Tom\")\n\tm.Store(\"mouse\", \"Jerry\")\n\n\t// 这里重新读取出来的，就是\n\tval, ok := m.Load(\"cat\")\n\tif ok {\n\t\tfmt.Println(len(val.(string)))\n\t}\n}"
  },
  {
    "path": "examples/third_lesson/sync/mutex.go",
    "content": "package main\n\nimport (\n\t\"sync\"\n)\n\nvar mutex sync.Mutex\nvar rwMutex sync.RWMutex\nfunc Mutex() {\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\t// 你的代码\n}\n\nfunc RwMutex()  {\n\t// 加读锁\n\trwMutex.RLock()\n\tdefer rwMutex.RUnlock()\n\n\t// 也可以加写锁\n\trwMutex.Lock()\n\tdefer rwMutex.Unlock()\n}\n\n// 不可重入例子\nfunc Failed1()  {\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\n\t// 这一句会死锁\n\t// 但是如果你只有一个goroutine，那么这一个会导致程序崩溃\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n}\n\n// 不可升级\nfunc Failed2()  {\n\trwMutex.RLock()\n\tdefer rwMutex.RUnlock()\n\n\t// 这一句会死锁\n\t// 但是如果你只有一个goroutine，那么这一个会导致程序崩溃\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n}\n"
  },
  {
    "path": "examples/third_lesson/sync/once.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\nfunc main() {\n\tPrintOnce()\n\tPrintOnce()\n\tPrintOnce()\n}\n\nvar once sync.Once\n\n// 这个方法，不管调用几次，只会输出一次\nfunc PrintOnce() {\n\tonce.Do(func() {\n\t\tfmt.Println(\"只输出一次\")\n\t})\n}\n"
  },
  {
    "path": "examples/third_lesson/sync/pool.go",
    "content": "package main\n\nimport \"sync\"\n\nfunc main() {\n\tpool := sync.Pool{\n\t\tNew: func() interface{}{\n\t\t\treturn &user{}\n\t}}\n\n\t// Get 返回的是 interface{}，所以需要类型断言\n\tu := pool.Get().(*user)\n\t// defer 还回去\n\tdefer pool.Put(u)\n\n\t// 紧接着重置 u 这个对象\n\tu.Reset(\"Tom\", \"my_email@qq.com\")\n\n\t// 下边就是使用 u 来完成你的业务逻辑\n}\n\ntype user struct {\n\tName string\n\tEmail string\n}\n\n// 一般来说，复用对象都要求我们取出来之后，\n// 重置里面的字段\nfunc (u *user) Reset(name string, email string)  {\n\tu.Email = email\n\tu.Name = name\n}"
  },
  {
    "path": "examples/third_lesson/sync/wait_group.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\nfunc main() {\n\tres := 0\n\twg := sync.WaitGroup{}\n\twg.Add(10)\n\tfor i := 0; i < 10; i++ {\n\t\tgo func(val int) {\n\t\t\tres += val\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\t// 把这个注释掉你会发现，什么结果你都可能拿到\n\twg.Wait()\n\tfmt.Println(res)\n}"
  },
  {
    "path": "go.mod",
    "content": "module geektime/toy-web\n\ngo 1.16\n\nrequire (\n\tgithub.com/hashicorp/golang-lru v0.5.4 // indirect\n\tgithub.com/stretchr/testify v1.7.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"geektime/toy-web/demo\"\n\t_ \"geektime/toy-web/demo/filters\"\n\t\"geektime/toy-web/pkg\"\n\t\"net/http\"\n\t\"time\"\n)\n\nfunc home(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"这是主页\")\n}\n\nfunc user(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"这是用户\")\n}\n\nfunc createUser(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"这是创建用户\")\n}\n\nfunc order(w http.ResponseWriter, r *http.Request)  {\n\tfmt.Fprintf(w, \"这是订单\")\n}\n\nfunc main() {\n\tshutdown := web.NewGracefulShutdown()\n\tserver := web.NewSdkHttpServer(\"my-test-server\",\n\t\tweb.MetricFilterBuilder, shutdown.ShutdownFilterBuilder)\n\tadminServer := web.NewSdkHttpServer(\"admin-test-server\",\n\t\t// 注意，如果你真实环境里面，使用的是多个 server监听不同端口，\n\t\t// 那么这个 shutdown最好也是多个。互相之间就不会有竞争\n\t\t// MetricFilterBuilder 是无状态的，所以不存在这种问题\n\t\tweb.MetricFilterBuilder, shutdown.ShutdownFilterBuilder)\n\n\t// 注册路由\n\t_ = server.Route(\"POST\", \"/user/create/*\", demo.SignUp)\n\t_ = server.Route(\"POST\", \"/slowService\", demo.SlowService)\n\n\t// 准备静态路由\n\n\tstaticHandler := web.NewStaticResourceHandler(\n\t\t\"demo/static\", \"/static\",\n\t\tweb.WithMoreExtension(map[string]string{\n\t\t\t\"mp3\": \"audio/mp3\",\n\t\t}), web.WithFileCache(1 << 20, 100))\n\t// 访问 Get http://localhost:8080/static/forest.png\n\tserver.Route(\"GET\", \"/static/*\", staticHandler.ServeStaticResource)\n\n\tgo func() {\n\t\tif err := adminServer.Start(\":8081\"); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tif err := server.Start(\":8080\"); err != nil {\n\t\t\t// 快速失败，因为服务器都没启动成功，啥也做不了\n\t\t\tpanic(err)\n\t\t}\n\t\t// 假设我们后面还有很多动作\n\t}()\n\n\t// 先执行 RejectNewRequestAndWaiting，等待所有的请求\n\t// 然后我们关闭 server，如果是多个 server，可以多个 goroutine 一起关闭\n\t//\n\tweb.WaitForShutdown(\n\t\tfunc(ctx context.Context) error {\n\t\t\t// 假设我们这里有一个 hook\n\t\t\t// 可以通知网关我们要下线了\n\t\t\tfmt.Println(\"mock notify gateway\")\n\t\t\ttime.Sleep(time.Second * 2)\n\t\t\treturn nil\n\t\t},\n\t\tshutdown.RejectNewRequestAndWaiting,\n\t\t// 全部请求处理完了我们就可以关闭 server了\n\t\tweb.BuildCloseServerHook(server, adminServer),\n\t\tfunc(ctx context.Context) error {\n\t\t\t// 假设这里我要清理一些执行过程中生成的临时资源\n\t\t\tfmt.Println(\"mock release resources\")\n\t\t\ttime.Sleep(time.Second * 2)\n\t\t\treturn nil\n\t\t})\n\n\t// filterNames := ReadFromConfig\n\t// 匿名引入之后，就可以在这里按名索引 filter\n\t//web.NewSdkHttpServerWithFilterNames(\"my-server\", filterNames...)\n\n}\n\n\n\n\n"
  },
  {
    "path": "onclass/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc home(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"这是主页\")\n}\n\nfunc user(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"这是用户\")\n}\n\nfunc createUser(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"这是创建用户\")\n}\n\nfunc order(w http.ResponseWriter, r *http.Request)  {\n\tfmt.Fprintf(w, \"这是订单\")\n}\n\n\nfunc main() {\n\thttp.HandleFunc(\"/\", home)\n\thttp.HandleFunc(\"/user\", user)\n\thttp.HandleFunc(\"/user/create\", createUser)\n\thttp.HandleFunc(\"/order\", order)\n\thttp.ListenAndServe(\":8080\", nil)\n}\n\ntype Server interface {\n\tRoute(pattern string, handlerFunc http.HandlerFunc)\n\tStart(address string) error\n}\n\ntype sdkHttpServer struct {\n\tName string\n}\n"
  },
  {
    "path": "pkg/context.go",
    "content": "package web\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype Context struct {\n\tW http.ResponseWriter\n\tR *http.Request\n\tPathParams map[string]string\n}\n\nfunc (c *Context) ReadJson(data interface{}) error {\n\tbody, err := io.ReadAll(c.R.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(body, data)\n}\nfunc (c *Context) OkJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusOK, data)\n}\n\nfunc (c *Context) SystemErrJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusInternalServerError, data)\n}\n\nfunc (c *Context) BadRequestJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusBadRequest, data)\n}\n\nfunc (c *Context) WriteJson(status int, data interface{}) error {\n\tc.W.WriteHeader(status)\n\tbs, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = c.W.Write(bs)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc NewContext(w http.ResponseWriter, r *http.Request) *Context {\n\treturn &Context{\n\t\tW: w,\n\t\tR: r,\n\t\t// 一般路径参数都是一个，所以容量1就可以了\n\t\tPathParams: make(map[string]string, 1),\n\t}\n}\n\nfunc newContext() *Context {\n\tfmt.Println(\"create new context\")\n\treturn &Context{\n\t}\n}\n\nfunc (c *Context) Reset(w http.ResponseWriter, r *http.Request) {\n\tc.W = w\n\tc.R = r\n\tc.PathParams = make(map[string]string, 1)\n}"
  },
  {
    "path": "pkg/filter.go",
    "content": "package web\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype FilterBuilder func(next Filter) Filter\n\ntype Filter func(c *Context)\n\nfunc MetricFilterBuilder(next Filter) Filter {\n\treturn func(c *Context) {\n\t\t// 执行前的时间\n\t\tstartTime := time.Now().UnixNano()\n\t\tnext(c)\n\t\t// 执行后的时间\n\t\tendTime := time.Now().UnixNano()\n\t\tfmt.Printf(\"run time: %d \\n\", endTime-startTime)\n\t}\n}\n\nvar builderMap = make(map[string]FilterBuilder, 4)\nfunc RegisterFilter(name string, builder FilterBuilder)  {\n\t// 情况1 有些时候你可能不允许重复注册，那么你要先检测是否已经注册过了\n\t// 情况2 你会在并发的环境下调用这个方法，那么你应该\n\tbuilderMap[name] = builder\n}\n\nfunc GetFilterBuilder(name string) FilterBuilder {\n\t// 如果你觉得名字必须是正确的，那么你同样需要检测\n\treturn builderMap[name]\n}"
  },
  {
    "path": "pkg/graceful_shutdown.go",
    "content": "package web\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nvar ErrorHookTimeout = errors.New(\"the hook timeout\")\n\ntype GracefulShutdown struct {\n\t// 还在处理中的请求数\n\treqCnt int64\n\t// 大于 1 就说明要关闭了\n\tclosing int32\n\t\n\t// 用 channel 来通知已经处理完了所有请求\n\tzeroReqCnt chan struct{}\n}\n\nfunc NewGracefulShutdown() *GracefulShutdown {\n\treturn &GracefulShutdown{\n\t\tzeroReqCnt: make(chan struct{}),\n\t}\n}\n\n// ShutdownFilterBuilder 这个东西怎么保持线程安全呢？\n// 它的逻辑有点绕，核心就在于当我们准备关闭的时候，这个动作是单向的，就是说，我的closing一旦加1\n// 就再也不会-1\n// 所以我们不需要用一个锁把整个方法锁住\n// 而实际上，基于这个理由，我们也不需要把 closing 声明为 int32\n// 只需要声明 bool，然后在关闭的时候设置为 true。在这里直接检测 true or false就可以。\n// 这种做法有一个很重要的点是，在设置值的时候，即便 bool 被高速缓存缓存了，\n// 即便了 bool 在平台上，处理器并不能一条指令 设置好值，\n// 但是也没什么关系。因为我们可以确认，最终 bool 会变为 true\n// 这个做法更加难以理解，所以采用了使用 closing int32 的做法\nfunc (g *GracefulShutdown) ShutdownFilterBuilder(next Filter) Filter {\n\treturn func(c *Context) {\n\t\t// 开始拒绝所有的请求\n\t\tcl :=  atomic.LoadInt32(&g.closing)\n\t\tif cl > 0 {\n\t\t\tc.W.WriteHeader(http.StatusServiceUnavailable)\n\t\t\treturn\n\t\t}\n\t\tatomic.AddInt64(&g.reqCnt, 1)\n\t\tnext(c)\n\t\tn := atomic.AddInt64(&g.reqCnt, -1)\n\t\t// 已经开始关闭了，而且请求数为0，\n\t\tif cl > 0 && n == 0 {\n\t\t\tg.zeroReqCnt <- struct{}{}\n\t\t}\n\t}\n}\n\n// RejectNewRequestAndWaiting 将会拒绝新的请求，并且等待处理中的请求\nfunc (g *GracefulShutdown) RejectNewRequestAndWaiting(ctx context.Context) error {\n\n\tatomic.AddInt32(&g.closing, 1)\n\n\t// 特殊 case 关闭之前其实就已经处理完了请求。\n\tif atomic.LoadInt64(&g.reqCnt) == 0 {\n\t\treturn nil\n\t}\n\t\n\tdone := ctx.Done()\n\t// 因为是单向的，所以我们这里不用 for 在外面包\n\t// 所谓单向就是，我一触发就回不到原来正常处理请求的状态了\n\t// 这个 select 可以理解为，要么超时了\n\t// 要么我这里所有的请求都执行完了\n\tselect {\n\tcase <- done:\n\t\tfmt.Println(\"超时了，还没等到所有请求执行完毕\")\n\t\treturn ErrorHookTimeout\n\tcase <- g.zeroReqCnt:\n\t\tfmt.Println(\"全部请求处理完了\")\n\t}\n\treturn nil\n}\n\nfunc WaitForShutdown(hooks...Hook) {\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, ShutdownSignals...)\n\tselect {\n\tcase sig := <-signals:\n\t\tfmt.Printf(\"get signal %s, application will shutdown \\n\", sig)\n\t\t// 十分钟都还不行，就直接强退了\n\t\ttime.AfterFunc(time.Minute * 10, func() {\n\t\t\tfmt.Printf(\"Shutdown gracefully timeout, application will shutdown immediately. \")\n\t\t\tos.Exit(1)\n\t\t})\n\t\tfor _, h := range hooks {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second * 30)\n\t\t\terr := h(ctx)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"failed to run hook, err: %v \\n\", err)\n\t\t\t}\n\t\t\tcancel()\n\t\t}\n\t\tos.Exit(0)\n\t}\n}\n"
  },
  {
    "path": "pkg/graceful_shutdown_signal_darwin.go",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage web\n\nimport (\n\t\"os\"\n\t\"syscall\"\n)\n\nvar (\n\t// ShutdownSignals receives shutdown signals to process\n\tShutdownSignals = []os.Signal{\n\t\tos.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP,\n\t\tsyscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,\n\t\tsyscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM,\n\t}\n)\n"
  },
  {
    "path": "pkg/graceful_shutdown_signal_linux.go",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage web\n\nimport (\n\t\"os\"\n\t\"syscall\"\n)\n\nvar (\n\t// ShutdownSignals receives shutdown signals to process\n\tShutdownSignals = []os.Signal{\n\t\tos.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP,\n\t\tsyscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,\n\t\tsyscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM,\n\t}\n\n\t// DumpHeapShutdownSignals receives shutdown signals to process\n\tDumpHeapShutdownSignals = []os.Signal{\n\t\tsyscall.SIGQUIT, syscall.SIGILL,\n\t\tsyscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS,\n\t}\n)\n"
  },
  {
    "path": "pkg/graceful_shutdown_signal_windows.go",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage web\n\nimport (\n\t\"os\"\n\t\"syscall\"\n)\n\nvar (\n\t// ShutdownSignals receives shutdown signals to process\n\tShutdownSignals = []os.Signal{\n\t\tos.Interrupt, os.Kill, syscall.SIGKILL,\n\t\tsyscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,\n\t\tsyscall.SIGABRT, syscall.SIGTERM,\n\t}\n)"
  },
  {
    "path": "pkg/handler.go",
    "content": "package web\n\ntype Handler interface {\n\tServeHTTP(c *Context)\n\tRoutable\n}\n\ntype handlerFunc func(c *Context)"
  },
  {
    "path": "pkg/hook.go",
    "content": "package web\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Hook 是一个钩子函数。注意，\n// ctx 是一个有超时机制的 context.Context\n// 所以你必须处理超时的问题\ntype Hook func(ctx context.Context) error\n\n// BuildCloseServerHook 这里其实可以考虑使用 errgroup，\n// 但是我们这里不用是希望每个 server 单独关闭\n// 互相之间不影响\nfunc BuildCloseServerHook(servers ...Server) Hook {\n\treturn func(ctx context.Context) error {\n\t\twg := sync.WaitGroup{}\n\t\tdoneCh := make(chan struct{})\n\t\twg.Add(len(servers))\n\n\t\tfor _, s := range servers {\n\t\t\tgo func(svr Server) {\n\t\t\t\terr := svr.Shutdown(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Printf(\"server shutdown error: %v \\n\", err)\n\t\t\t\t}\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\twg.Done()\n\t\t\t}(s)\n\t\t}\n\t\tgo func() {\n\t\t\twg.Wait()\n\t\t\tdoneCh <- struct{}{}\n\t\t}()\n\t\tselect {\n\t\tcase <- ctx.Done():\n\t\t\tfmt.Printf(\"closing servers timeout \\n\")\n\t\t\treturn ErrorHookTimeout\n\t\tcase <- doneCh:\n\t\t\tfmt.Printf(\"close all servers \\n\")\n\t\t\treturn nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/hook_test.go",
    "content": "package web\n\nimport (\n\t\"context\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestBuildCloseServerHook(t *testing.T) {\n\tsvr := NewSdkHttpServer(\"test-sever\")\n\th := BuildCloseServerHook(svr, svr, svr, svr, svr)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second * 10)\n\tdefer cancel()\n\terr := h(ctx)\n\tassert.Nil(t, err)\n\n\tctx, cancel = context.WithTimeout(context.Background(), time.Millisecond * 10)\n\tdefer cancel()\n\terr = h(ctx)\n\tassert.Equal(t, ErrorHookTimeout, err)\n}\n"
  },
  {
    "path": "pkg/map_router.go",
    "content": "package web\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n)\n\n// 一种常用的GO设计模式，\n// 用于确保HandlerBasedOnMap肯定实现了这个接口\nvar _ Handler = &HandlerBasedOnMap{}\n\n\ntype HandlerBasedOnMap struct {\n\thandlers sync.Map\n}\n\nfunc (h *HandlerBasedOnMap) ServeHTTP(c *Context) {\n\trequest := c.R\n\tkey := h.key(request.Method, request.URL.Path)\n\thandler, ok := h.handlers.Load(key)\n\tif !ok {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"not any router match\"))\n\t\treturn\n\t}\n\n\thandler.(handlerFunc)(c)\n}\n\nfunc (h *HandlerBasedOnMap) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\tkey := h.key(method, pattern)\n\th.handlers.Store(key, handlerFunc)\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnMap) key(method string,\n\tpath string) string {\n\treturn fmt.Sprintf(\"%s#%s\", method, path)\n}\n\nfunc NewHandlerBasedOnMap() *HandlerBasedOnMap {\n\treturn &HandlerBasedOnMap{}\n}\n"
  },
  {
    "path": "pkg/server.go",
    "content": "package web\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Routable 可路由的\ntype Routable interface {\n\t// Route 设定一个路由，命中该路由的会执行handlerFunc的代码\n\tRoute(method string, pattern string, handlerFunc handlerFunc) error\n}\n\n// Server 是http server 的顶级抽象\ntype Server interface {\n\tRoutable\n\t// Start 启动我们的服务器\n\tStart(address string) error\n\n\tShutdown(ctx context.Context) error\n}\n\n// sdkHttpServer 这个是基于 net/http 这个包实现的 http server\ntype sdkHttpServer struct {\n\t// Name server 的名字，给个标记，日志输出的时候用得上\n\tName    string\n\thandler Handler\n\troot    Filter\n\tctxPool sync.Pool\n}\n\nfunc (s *sdkHttpServer) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\treturn s.handler.Route(method, pattern, handlerFunc)\n}\n\nfunc (s *sdkHttpServer) Start(address string) error {\n\treturn http.ListenAndServe(address, s)\n}\n\nfunc (s *sdkHttpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request)  {\n\tc := s.ctxPool.Get().(*Context)\n\tdefer func() {\n\t\ts.ctxPool.Put(c)\n\t}()\n\tc.Reset(writer, request)\n\ts.root(c)\n}\n\nfunc (s *sdkHttpServer) Shutdown(ctx context.Context) error {\n\t// 因为我们这个简单的框架，没有什么要清理的，\n\t// 所以我们 sleep 一下来模拟这个过程\n\tfmt.Printf(\"%s shutdown...\\n\", s.Name)\n\ttime.Sleep(time.Second)\n\tfmt.Printf(\"%s shutdown!!!\\n\", s.Name)\n\treturn nil\n}\n\nfunc NewSdkHttpServer(name string, builders ...FilterBuilder) Server {\n\n\t// 改用我们的树\n\thandler := NewHandlerBasedOnTree()\n\t//handler := NewHandlerBasedOnMap()\n\t// 因为我们是一个链，所以我们把最后的业务逻辑处理，也作为一环\n\tvar root Filter = handler.ServeHTTP\n\t// 从后往前把filter串起来\n\tfor i := len(builders) - 1; i >= 0; i-- {\n\t\tb := builders[i]\n\t\troot = b(root)\n\t}\n\tres := &sdkHttpServer{\n\t\tName: name,\n\t\thandler: handler,\n\t\troot: root,\n\t\tctxPool: sync.Pool{New: func() interface {}{\n\t\t\treturn newContext()\n\t\t}},\n\t}\n\treturn res\n}\n\nfunc NewSdkHttpServerWithFilterNames(name string,\n\tfilterNames...string) Server {\n\t// 这里取出来\n\tbuilders := make([]FilterBuilder, 0, len(filterNames))\n\tfor _, n := range filterNames {\n\t\tb := GetFilterBuilder(n)\n\t\tbuilders = append(builders, b)\n\t}\n\n\treturn NewSdkHttpServer(name, builders...)\n}\n\n"
  },
  {
    "path": "pkg/static_resource.go",
    "content": "package web\n\nimport (\n\t\"fmt\"\n\tlru \"github.com/hashicorp/golang-lru\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype StaticResourceHandlerOption func(h *StaticResourceHandler)\n\ntype StaticResourceHandler struct {\n\tdir string\n\tpathPrefix string\n\textensionContentTypeMap map[string]string\n\n\t// 缓存静态资源的限制\n\tcache *lru.Cache\n\tmaxFileSize int\n}\n\ntype fileCacheItem struct {\n\tfileName string\n\tfileSize int\n\tcontentType string\n\tdata []byte\n}\n\nfunc NewStaticResourceHandler(dir string, pathPrefix string,\n\toptions...StaticResourceHandlerOption) *StaticResourceHandler {\n\tres := &StaticResourceHandler{\n\t\tdir: dir,\n\t\tpathPrefix: pathPrefix,\n\t\textensionContentTypeMap: map[string]string{\n\t\t\t// 这里根据自己的需要不断添加\n\t\t\t\"jpeg\": \"image/jpeg\",\n\t\t\t\"jpe\": \"image/jpeg\",\n\t\t\t\"jpg\": \"image/jpeg\",\n\t\t\t\"png\": \"image/png\",\n\t\t\t\"pdf\": \"image/pdf\",\n\t\t},\n\t}\n\n\tfor _, o := range options {\n\t\to(res)\n\t}\n\treturn res\n}\n// WithFileCache 静态文件将会被缓存\n// maxFileSizeThreshold 超过这个大小的文件，就被认为是大文件，我们将不会缓存\n// maxCacheFileCnt 最多缓存多少个文件\n// 所以我们最多缓存 maxFileSizeThreshold * maxCacheFileCnt\nfunc WithFileCache(maxFileSizeThreshold int, maxCacheFileCnt int) StaticResourceHandlerOption {\n\treturn func(h *StaticResourceHandler) {\n\t\tc, err := lru.New(maxCacheFileCnt)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"could not create LRU, we won't cache static file\")\n\t\t}\n\t\th.maxFileSize = maxFileSizeThreshold\n\t\th.cache = c\n\t}\n}\n\nfunc WithMoreExtension(extMap map[string]string) StaticResourceHandlerOption {\n\treturn func(h *StaticResourceHandler) {\n\t\tfor ext, contentType := range extMap {\n\t\t\th.extensionContentTypeMap[ext] = contentType\n\t\t}\n\t}\n}\n\nfunc (h *StaticResourceHandler) ServeStaticResource(c *Context)  {\n\treq := strings.TrimPrefix(c.R.URL.Path, h.pathPrefix)\n\tif item, ok := h.readFileFromData(req); ok {\n\t\tfmt.Printf(\"read data from cache...\")\n\t\th.writeItemAsResponse(item, c.W)\n\t\treturn\n\t}\n\tpath := filepath.Join(h.dir, req)\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\tc.W.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\text := getFileExt(f.Name())\n\tt, ok := h.extensionContentTypeMap[ext]\n\tif !ok {\n\t\tc.W.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tdata, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tc.W.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\titem := &fileCacheItem{\n\t\tfileSize: len(data),\n\t\tdata: data,\n\t\tcontentType: t,\n\t\tfileName: req,\n\t}\n\n\th.cacheFile(item)\n\th.writeItemAsResponse(item, c.W)\n\n}\n\nfunc (h *StaticResourceHandler) cacheFile(item *fileCacheItem) {\n\tif h.cache != nil && item.fileSize < h.maxFileSize {\n\t\th.cache.Add(item.fileName, item)\n\t}\n}\n\nfunc (h *StaticResourceHandler) writeItemAsResponse(item *fileCacheItem, writer http.ResponseWriter) {\n\twriter.WriteHeader(http.StatusOK)\n\twriter.Header().Set(\"Content-Type\", item.contentType)\n\twriter.Header().Set(\"Content-Length\", fmt.Sprintf(\"%d\", item.fileSize))\n\t_, _ = writer.Write(item.data)\n\n}\n\nfunc (h *StaticResourceHandler) readFileFromData(fileName string) (*fileCacheItem, bool) {\n\tif h.cache != nil {\n\t\tif item, ok := h.cache.Get(fileName); ok {\n\t\t\treturn item.(*fileCacheItem), true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc getFileExt(name string) string {\n\tindex := strings.LastIndex(name, \".\")\n\tif index == len(name) - 1{\n\t\treturn \"\"\n\t}\n\treturn name[index+1:]\n}\n"
  },
  {
    "path": "pkg/tree_node.go",
    "content": "package web\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\n\t// 根节点，只有根用这个\n\tnodeTypeRoot = iota\n\n\t// *\n\tnodeTypeAny\n\n\t// 路径参数\n\tnodeTypeParam\n\n\t// 正则\n\tnodeTypeReg\n\n\t// 静态，即完全匹配\n\tnodeTypeStatic\n)\n\nconst any = \"*\"\n\n// matchFunc 承担两个职责，一个是判断是否匹配，一个是在匹配之后\n// 将必要的数据写入到 Context\n// 所谓必要的数据，这里基本上是指路径参数\ntype matchFunc func(path string, c *Context) bool\n\ntype node struct {\n\tchildren []*node\n\n\t// 如果这是叶子节点，\n\t// 那么匹配上之后就可以调用该方法\n\thandler   handlerFunc\n\tmatchFunc matchFunc\n\n\t// 原始的 pattern。注意，它不是完整的pattern，\n\t// 而是匹配到这个节点的pattern\n\tpattern string\n\tnodeType int\n}\n\n// 静态节点\nfunc newStaticNode(path string) *node {\n\treturn &node{\n\t\tchildren: make([]*node, 0, 2),\n\t\tmatchFunc: func(p string, c *Context) bool {\n\t\t\treturn path == p && p != \"*\"\n\t\t},\n\t\tnodeType: nodeTypeStatic,\n\t\tpattern:  path,\n\t}\n}\n\n\nfunc newRootNode(method string) *node {\n\treturn &node{\n\t\tchildren: make([]*node, 0, 2),\n\t\tmatchFunc: func( p string, c *Context) bool {\n\t\t\tpanic(\"never call me\")\n\t\t},\n\t\tnodeType: nodeTypeRoot,\n\t\tpattern:  method,\n\t}\n}\n\nfunc newNode(path string) *node {\n\tif path == \"*\"{\n\t\treturn newAnyNode()\n\t}\n\tif strings.HasPrefix(path, \":\") {\n\t\treturn newParamNode(path)\n\t}\n\treturn newStaticNode(path)\n}\n\n// 通配符 * 节点\nfunc newAnyNode() *node {\n\treturn &node{\n\t\t// 因为我们不允许 * 后面还有节点，所以这里可以不用初始化\n\t\t//children: make([]*node, 0, 2),\n\t\tmatchFunc: func(p string, c *Context) bool {\n\t\t\treturn true\n\t\t},\n\t\tnodeType: nodeTypeAny,\n\t\tpattern:  any,\n\t}\n}\n\n// 路径参数节点\nfunc newParamNode(path string) *node {\n\tparamName := path[1:]\n\treturn &node{\n\t\tchildren: make([]*node, 0, 2),\n\t\tmatchFunc: func(p string, c *Context) bool {\n\t\t\tif c != nil {\n\t\t\t\tc.PathParams[paramName] = p\n\t\t\t}\n\t\t\t// 如果自身是一个参数路由，\n\t\t\t// 然后又来一个通配符，我们认为是不匹配的\n\t\t\treturn p != any\n\t\t},\n\t\tnodeType: nodeTypeParam,\n\t\tpattern:  path,\n\t}\n}\n\n// 正则节点\n//func newRegNode(path string) *node {\n//\t// 依据你的规则拿到正则表达式\n//\treturn &node{\n//\t\tchildren: make([]*node, 0, 2),\n//\t\tmatchFunc: func(p string, c *Context) bool {\n//\t\t\t// 怎么写？\n//\t\t},\n//\t\tnodeType: nodeTypeParam,\n//\t\tpattern: path,\n//\t}\n//}\n\n"
  },
  {
    "path": "pkg/tree_router.go",
    "content": "package web\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n)\n\nvar ErrorInvalidRouterPattern = errors.New(\"invalid router pattern\")\nvar ErrorInvalidMethod = errors.New(\"invalid method\")\n\ntype HandlerBasedOnTree struct {\n\tforest map[string]*node\n}\n\nvar supportMethods = [4]string {\n\thttp.MethodGet,\n\thttp.MethodPost,\n\thttp.MethodPut,\n\thttp.MethodDelete,\n}\n\nfunc NewHandlerBasedOnTree() Handler {\n\tforest := make(map[string]*node, len(supportMethods))\n\tfor _, m :=range supportMethods {\n\t\tforest[m] = newRootNode(m)\n\t}\n\treturn &HandlerBasedOnTree{\n\t\tforest: forest,\n\t}\n}\n\n// ServeHTTP 就是从树里面找节点\n// 找到了就执行\nfunc (h *HandlerBasedOnTree) ServeHTTP(c *Context) {\n\thandler, found := h.findRouter(c.R.Method, c.R.URL.Path, c)\n\tif !found {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n\t\treturn\n\t}\n\thandler(c)\n}\n\nfunc (h *HandlerBasedOnTree) findRouter(method, path string, c *Context) (handlerFunc, bool) {\n\t// 去除头尾可能有的/，然后按照/切割成段\n\tpaths := strings.Split(strings.Trim(path, \"/\"), \"/\")\n\tcur, ok := h.forest[method]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\tfor _, p := range paths {\n\t\t// 从子节点里边找一个匹配到了当前 p 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, p, c)\n\t\tif !found {\n\t\t\treturn nil, false\n\t\t}\n\t\tcur = matchChild\n\t}\n\t// 到这里，应该是找完了\n\tif cur.handler == nil {\n\t\t// 到达这里是因为这种场景\n\t\t// 比如说你注册了 /user/profile\n\t\t// 然后你访问 /user\n\t\treturn nil, false\n\t}\n\treturn cur.handler, true\n}\n\n// Route 就相当于往树里面插入节点\nfunc (h *HandlerBasedOnTree) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\n\terr := h.validatePattern(pattern)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 将pattern按照URL的分隔符切割\n\t// 例如，/user/friends 将变成 [user, friends]\n\t// 将前后的/去掉，统一格式\n\tpattern = strings.Trim(pattern, \"/\")\n\tpaths := strings.Split(pattern, \"/\")\n\n\t// 当前指向根节点\n\tcur, ok := h.forest[method]\n\tif !ok {\n\t\treturn ErrorInvalidMethod\n\t}\n\tfor index, path := range paths {\n\n\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, path, nil)\n\t\t// != nodeTypeAny 是考虑到 /order/* 和 /order/:id 这种注册顺序\n\t\tif found && matchChild.nodeType != nodeTypeAny {\n\t\t\tcur = matchChild\n\t\t} else {\n\t\t\t// 为当前节点根据\n\t\t\th.createSubTree(cur, paths[index:], handlerFunc)\n\t\t\treturn nil\n\t\t}\n\t}\n\t// 离开了循环，说明我们加入的是短路径，\n\t// 比如说我们先加入了 /order/detail\n\t// 再加入/order，那么会走到这里\n\tcur.handler = handlerFunc\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnTree) validatePattern(pattern string) error {\n\t// 校验 *，如果存在，必须在最后一个，并且它前面必须是/\n\t// 即我们只接受 /* 的存在，abc*这种是非法\n\n\tpos := strings.Index(pattern, \"*\")\n\t// 找到了 *\n\tif pos > 0 {\n\t\t// 必须是最后一个\n\t\tif pos != len(pattern) - 1 {\n\t\t\treturn ErrorInvalidRouterPattern\n\t\t}\n\t\tif pattern[pos-1] != '/' {\n\t\t\treturn ErrorInvalidRouterPattern\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnTree) findMatchChild(root *node,\n\tpath string, c *Context) (*node, bool) {\n\tcandidates := make([]*node, 0, 2)\n\tfor _, child := range root.children {\n\t\tif child.matchFunc(path, c) {\n\t\t\tcandidates = append(candidates, child)\n\t\t}\n\t}\n\n\tif len(candidates) == 0 {\n\t\treturn nil, false\n\t}\n\n\t// type 也决定了它们的优先级\n\tsort.Slice(candidates, func(i, j int) bool {\n\t\treturn candidates[i].nodeType < candidates[j].nodeType\n\t})\n\treturn candidates[len(candidates) - 1], true\n}\n\nfunc (h *HandlerBasedOnTree) createSubTree(root *node, paths []string, handlerFn handlerFunc) {\n\tcur := root\n\tfor _, path := range paths {\n\t\tnn := newNode(path)\n\t\tcur.children = append(cur.children, nn)\n\t\tcur = nn\n\t}\n\tcur.handler = handlerFn\n}\n\n"
  },
  {
    "path": "pkg/tree_router_test.go",
    "content": "package web\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestHandlerBasedOnTree_Route(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\t// 要确认已经为支持的方法创建了节点\n\tassert.Equal(t, len(supportMethods), len(handler.forest))\n\n\tpostNode := handler.forest[http.MethodPost]\n\n\terr := handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(postNode.children))\n\n\tn := postNode.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.pattern)\n\tassert.NotNil(t, n.handler)\n\tassert.Empty(t, n.children)\n\n\t// 我们只有\n\t//  user -> profile\n\terr = handler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(n.children))\n\tprofileNode := n.children[0]\n\tassert.NotNil(t, profileNode)\n\tassert.Equal(t, \"profile\", profileNode.pattern)\n\tassert.NotNil(t, profileNode.handler)\n\tassert.Empty(t, profileNode.children)\n\n\t// 试试重复\n\terr = handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tn = postNode.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.pattern)\n\tassert.NotNil(t, n.handler)\n\t// 有profile节点\n\tassert.Equal(t, 1, len(n.children))\n\n\t// 给 user 再加一个节点\n\terr = handler.Route(http.MethodPost, \"/user/home\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(n.children))\n\thomeNode := n.children[1]\n\tassert.NotNil(t, homeNode)\n\tassert.Equal(t, \"home\", homeNode.pattern)\n\tassert.NotNil(t, homeNode.handler)\n\tassert.Empty(t, homeNode.children)\n\n\t// 添加 /order/detail\n\terr = handler.Route(http.MethodPost, \"/order/detail\", func(c *Context) {})\n\tassert.Equal(t, 2, len(postNode.children))\n\torderNode := postNode.children[1]\n\tassert.NotNil(t, orderNode)\n\tassert.Equal(t, \"order\", orderNode.pattern)\n\t// 此刻我们只有/order/detail，但是没有/order\n\tassert.Nil(t, orderNode.handler)\n\tassert.Equal(t, 1, len(orderNode.children))\n\n\torderDetailNode := orderNode.children[0]\n\tassert.NotNil(t, orderDetailNode)\n\tassert.Empty(t, orderDetailNode.children)\n\tassert.Equal(t, \"detail\", orderDetailNode.pattern)\n\tassert.NotNil(t, orderDetailNode.handler)\n\n\t// 加一个 /order\n\terr = handler.Route(http.MethodPost, \"/order\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(postNode.children))\n\torderNode = postNode.children[1]\n\tassert.Equal(t, \"order\", orderNode.pattern)\n\t// 此时我们有了 /order\n\tassert.NotNil(t, orderNode.handler)\n\n\terr = handler.Route(http.MethodPost, \"/order/*\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(orderNode.children))\n\torderWildcard := orderNode.children[1]\n\tassert.NotNil(t, orderWildcard)\n\tassert.NotNil(t, orderWildcard.handler)\n\tassert.Equal(t, \"*\", orderWildcard.pattern)\n\n\terr = handler.Route(http.MethodPost, \"/order/*/checkout\", func(c *Context) {})\n\tassert.Equal(t, ErrorInvalidRouterPattern, err)\n\n\terr = handler.Route(http.MethodConnect, \"/order/checkout\", func(c *Context) {})\n\tassert.Equal(t, ErrorInvalidMethod, err)\n\n\terr = handler.Route(http.MethodPost, \"/order/:id\", func(c *Context){})\n\tassert.Nil(t, err)\n\t// 这时候我们有/order/* 和 /order/:id\n\t// 因为我们并没有认为它们不兼容，而是/order/:id优先\n\tassert.Equal(t, 3, len(orderNode.children))\n\torderParamNode := orderNode.children[2]\n\tassert.Equal(t, \":id\", orderParamNode.pattern)\n\n}\n\nfunc TestHandlerBasedOnTree_findRouter(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\t_ = handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tctx := NewContext(nil, nil)\n\tfn, found := handler.findRouter(http.MethodPost, \"/user\", ctx)\n\tassert.True(t, found)\n\tassert.NotNil(t, fn)\n\t_, found = handler.findRouter(http.MethodPost,\"/user/profile\", ctx)\n\tassert.False(t, found)\n\n\t_ = handler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\t_, found = handler.findRouter(http.MethodPost, \"/user/profile\", ctx)\n\tassert.True(t, found)\n\n\t_, found = handler.findRouter(http.MethodPost, \"/user\", ctx)\n\tassert.True(t, found)\n\n\tvar detailHandler handlerFunc = func(c *Context) {}\n\t_ = handler.Route(http.MethodPost, \"/order/detail\", detailHandler)\n\t_, found = handler.findRouter(http.MethodPost,\"/order\", ctx)\n\tassert.False(t, found)\n\n\tfn, found = handler.findRouter(http.MethodPost,\"/order/detail\", ctx)\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(detailHandler, fn))\n\n\tvar wildcardHandler handlerFunc = func(c *Context) {}\n\t_ = handler.Route(http.MethodPost, \"/order/*\", wildcardHandler)\n\t_, found = handler.findRouter(http.MethodPost,\"/order\", ctx)\n\tassert.False(t, found)\n\n\tfn, found = handler.findRouter(http.MethodPost,\"/order/detail\", ctx)\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(detailHandler, fn))\n\n\tfn, found = handler.findRouter(http.MethodPost,\"/order/checkout\", ctx)\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(wildcardHandler, fn))\n\n\t_, found = handler.findRouter(http.MethodGet,\"/order/checkout\", ctx)\n\tassert.False(t, found)\n\n\t// 参数路由\n\thandler.Route(http.MethodPost, \"/order/*\", wildcardHandler)\n}\n\nfunc handlerFuncEquals(hf1 handlerFunc, hf2 handlerFunc) bool {\n\treturn reflect.ValueOf(hf1).Pointer() == reflect.ValueOf(hf2).Pointer()\n}"
  },
  {
    "path": "pkg/v1/context.go",
    "content": "package webv1\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype Context struct {\n\tW http.ResponseWriter\n\tR *http.Request\n}\n\nfunc (c *Context) ReadJson(data interface{}) error {\n\tbody, err := io.ReadAll(c.R.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(body, data)\n}\nfunc (c *Context) OkJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusOK, data)\n}\n\nfunc (c *Context) SystemErrJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusInternalServerError, data)\n}\n\nfunc (c *Context) BadRequestJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusBadRequest, data)\n}\n\nfunc (c *Context) WriteJson(status int, data interface{}) error {\n\tbs, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = c.W.Write(bs)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.W.WriteHeader(status)\n\treturn nil\n}\n\nfunc NewContext(w http.ResponseWriter, r *http.Request) *Context {\n\treturn &Context{\n\t\tW: w,\n\t\tR: r,\n\t}\n}"
  },
  {
    "path": "pkg/v1/filter.go",
    "content": "package webv1\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype FilterBuilder func(next Filter) Filter\n\ntype Filter func(c *Context)\n\nfunc MetricFilterBuilder(next Filter) Filter {\n\treturn func(c *Context) {\n\t\t// 执行前的时间\n\t\tstartTime := time.Now().UnixNano()\n\t\tnext(c)\n\t\t// 执行后的时间\n\t\tendTime := time.Now().UnixNano()\n\t\tfmt.Printf(\"run time: %d \\n\", endTime-startTime)\n\t}\n}"
  },
  {
    "path": "pkg/v1/handler.go",
    "content": "package webv1\n\ntype Handler interface {\n\tServeHTTP(c *Context)\n\tRoutable\n}\n\ntype handlerFunc func(c *Context)"
  },
  {
    "path": "pkg/v1/map_router.go",
    "content": "package webv1\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n)\n\n// 一种常用的GO设计模式，\n// 用于确保HandlerBasedOnMap肯定实现了这个接口\nvar _ Handler = &HandlerBasedOnMap{}\n\n\ntype HandlerBasedOnMap struct {\n\thandlers sync.Map\n}\n\nfunc (h *HandlerBasedOnMap) ServeHTTP(c *Context) {\n\trequest := c.R\n\tkey := h.key(request.Method, request.URL.Path)\n\thandler, ok := h.handlers.Load(key)\n\tif !ok {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"not any router match\"))\n\t\treturn\n\t}\n\n\thandler.(handlerFunc)(c)\n}\n\nfunc (h *HandlerBasedOnMap) Route(method string, pattern string,\n\thandlerFunc handlerFunc) {\n\tkey := h.key(method, pattern)\n\th.handlers.Store(key, handlerFunc)\n}\n\nfunc (h *HandlerBasedOnMap) key(method string,\n\tpath string) string {\n\treturn fmt.Sprintf(\"%s#%s\", method, path)\n}\n\nfunc NewHandlerBasedOnMap() *HandlerBasedOnMap {\n\treturn &HandlerBasedOnMap{}\n}\n"
  },
  {
    "path": "pkg/v1/server.go",
    "content": "package webv1\n\nimport (\n\t\"net/http\"\n)\n\n// Routable 可路由的\ntype Routable interface {\n\t// Route 设定一个路由，命中该路由的会执行handlerFunc的代码\n\tRoute(method string, pattern string, handlerFunc handlerFunc)\n}\n\n// Server 是http server 的顶级抽象\ntype Server interface {\n\tRoutable\n\t// Start 启动我们的服务器\n\tStart(address string) error\n}\n\n// sdkHttpServer 这个是基于 net/http 这个包实现的 http server\ntype sdkHttpServer struct {\n\t// Name server 的名字，给个标记，日志输出的时候用得上\n\tName string\n\thandler Handler\n\troot Filter\n}\n\nfunc (s *sdkHttpServer) Route(method string, pattern string,\n\thandlerFunc handlerFunc) {\n\ts.handler.Route(method, pattern, handlerFunc)\n}\n\nfunc (s *sdkHttpServer) Start(address string) error {\n\thttp.HandleFunc(\"/\", func(writer http.ResponseWriter,\n\t\trequest *http.Request) {\n\t\tc := NewContext(writer, request)\n\t\ts.root(c)\n\t})\n\treturn http.ListenAndServe(address, nil)\n}\n\nfunc NewSdkHttpServer(name string, builders ...FilterBuilder) Server {\n\n\t// 改用我们的树\n\thandler := NewHandlerBasedOnTree()\n\t//handler := NewHandlerBasedOnMap()\n\t// 因为我们是一个链，所以我们把最后的业务逻辑处理，也作为一环\n\tvar root Filter = handler.ServeHTTP\n\t// 从后往前把filter串起来\n\tfor i := len(builders) - 1; i >= 0; i-- {\n\t\tb := builders[i]\n\t\troot = b(root)\n\t}\n\tres := &sdkHttpServer{\n\t\tName: name,\n\t\thandler: handler,\n\t\troot: root,\n\t}\n\treturn res\n}\n\n"
  },
  {
    "path": "pkg/v1/tree_router.go",
    "content": "package webv1\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\ntype HandlerBasedOnTree struct {\n\troot *node\n\n}\n\nfunc NewHandlerBasedOnTree() Handler {\n\treturn &HandlerBasedOnTree{\n\t\troot: &node{},\n\t}\n}\n\n// ServeHTTP 就是从树里面找节点\n// 找到了就执行\nfunc (h *HandlerBasedOnTree) ServeHTTP(c *Context) {\n\thandler, found := h.findRouter(c.R.URL.Path)\n\tif !found {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n\t\treturn\n\t}\n\thandler(c)\n}\n\n// 这个是不好测试的版本，可以尝试为这个写单元测试\n// 会发现很难构造 request，也很难对 ResponseWriter做断言\n//func (h *HandlerBasedOnTree) ServeHTTP(c *Context) {\n//\turl := strings.Trim(c.R.URL.Path, \"/\")\n//\tpaths := strings.Split(url, \"/\")\n//\tcur := h.root\n//\tfor _, path := range paths {\n//\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n//\t\tmatchChild, found := h.findMatchChild(cur, path)\n//\t\tif !found {\n//\t\t\t// 找不到匹配的路径，直接返回\n//\t\t\tc.W.WriteHeader(404)\n//\t\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n//\t\t\treturn\n//\t\t}\n//\t\tcur = matchChild\n//\t}\n//\t// 到这里，应该是找完了\n//\tif cur.handler == nil {\n//\t\t// 到达这里是因为这种场景\n//\t\t// 比如说你注册了 /user/profile\n//\t\t// 然后你访问 /user\n//\t\tc.W.WriteHeader(404)\n//\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n//\t\treturn\n//\t}\n//\tcur.handler(c)\n//}\n\nfunc (h *HandlerBasedOnTree) findRouter(path string) (handlerFunc, bool) {\n\t// 去除头尾可能有的/，然后按照/切割成段\n\tpaths := strings.Split(strings.Trim(path, \"/\"), \"/\")\n\tcur := h.root\n\tfor _, p := range paths {\n\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, p)\n\t\tif !found {\n\t\t\treturn nil, false\n\t\t}\n\t\tcur = matchChild\n\t}\n\t// 到这里，应该是找完了\n\tif cur.handler == nil {\n\t\t// 到达这里是因为这种场景\n\t\t// 比如说你注册了 /user/profile\n\t\t// 然后你访问 /user\n\t\treturn nil, false\n\t}\n\treturn cur.handler, true\n}\n\n// Route 就相当于往树里面插入节点\nfunc (h *HandlerBasedOnTree) Route(method string, pattern string,\n\thandlerFunc handlerFunc) {\n\t// 将pattern按照URL的分隔符切割\n\t// 例如，/user/friends 将变成 [user, friends]\n\t// 将前后的/去掉，统一格式\n\tpattern = strings.Trim(pattern, \"/\")\n\tpaths := strings.Split(pattern, \"/\")\n\t// 当前指向根节点\n\tcur := h.root\n\tfor index, path := range paths {\n\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, path)\n\t\tif found {\n\t\t\tcur = matchChild\n\t\t} else {\n\t\t\t// 为当前节点根据\n\t\t\th.createSubTree(cur, paths[index:], handlerFunc)\n\t\t\treturn\n\t\t}\n\t}\n\t// 离开了循环，说明我们加入的是短路径，\n\t// 比如说我们先加入了 /order/detail\n\t// 再加入/order，那么会走到这里\n\tcur.handler = handlerFunc\n}\n\nfunc (h *HandlerBasedOnTree) findMatchChild(root *node, path string) (*node, bool) {\n\tfor _, child := range root.children {\n\t\tif child.path == path {\n\t\t\treturn child, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc (h *HandlerBasedOnTree) createSubTree(root *node, paths []string, handlerFn handlerFunc) {\n\tcur := root\n\tfor _, path := range paths {\n\t\tnn := newNode(path)\n\t\tcur.children = append(cur.children, nn)\n\t\tcur = nn\n\t}\n\tcur.handler = handlerFn\n}\n\ntype node struct {\n\tpath string\n\tchildren []*node\n\n\t// 如果这是叶子节点，\n\t// 那么匹配上之后就可以调用该方法\n\thandler handlerFunc\n}\n\nfunc newNode(path string) *node {\n\treturn &node{\n\t\tpath: path,\n\t\tchildren: make([]*node, 0, 2),\n\t}\n}\n"
  },
  {
    "path": "pkg/v1/tree_router_test.go",
    "content": "package webv1\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestHandlerBasedOnTree_Route(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\tassert.NotNil(t, handler.root)\n\n\thandler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\n\t// 开始做断言，这个时候我们应该确认，在根节点之下只有一个user节点\n\tassert.Equal(t, 1, len(handler.root.children))\n\n\tn := handler.root.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.path)\n\tassert.NotNil(t, n.handler)\n\tassert.Empty(t, n.children)\n\n\t// 我们只有\n\t//  user -> profile\n\thandler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\tassert.Equal(t, 1, len(n.children))\n\tprofileNode := n.children[0]\n\tassert.NotNil(t, profileNode)\n\tassert.Equal(t, \"profile\", profileNode.path)\n\tassert.NotNil(t, profileNode.handler)\n\tassert.Empty(t, profileNode.children)\n\n\t// 试试重复\n\thandler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tn = handler.root.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.path)\n\tassert.NotNil(t, n.handler)\n\t// 有profile节点\n\tassert.Equal(t, 1, len(n.children))\n\n\t// 给 user 再加一个节点\n\thandler.Route(http.MethodPost, \"/user/home\", func(c *Context) {})\n\tassert.Equal(t, 2, len(n.children))\n\thomeNode := n.children[1]\n\tassert.NotNil(t, homeNode)\n\tassert.Equal(t, \"home\", homeNode.path)\n\tassert.NotNil(t, homeNode.handler)\n\tassert.Empty(t, homeNode.children)\n\n\t// 添加 /order/detail\n\thandler.Route(http.MethodPost, \"/order/detail\", func(c *Context) {})\n\tassert.Equal(t, 2, len(handler.root.children))\n\torderNode := handler.root.children[1]\n\tassert.NotNil(t, orderNode)\n\tassert.Equal(t, \"order\", orderNode.path)\n\t// 此刻我们只有/order/detail，但是没有/order\n\tassert.Nil(t, orderNode.handler)\n\tassert.Equal(t, 1, len(orderNode.children))\n\n\torderDetailNode := orderNode.children[0]\n\tassert.NotNil(t, orderDetailNode)\n\tassert.Empty(t, orderDetailNode.children)\n\tassert.Equal(t, \"detail\", orderDetailNode.path)\n\tassert.NotNil(t, orderDetailNode.handler)\n\n\t// 加一个 /order\n\thandler.Route(http.MethodPost, \"/order\", func(c *Context) {})\n\tassert.Equal(t, 2, len(handler.root.children))\n\torderNode = handler.root.children[1]\n\tassert.Equal(t, \"order\", orderNode.path)\n\t// 此时我们有了 /order\n\tassert.NotNil(t, orderNode.handler)\n\n}\n\nfunc TestHandlerBasedOnTree_findRouter(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\thandler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\t_, found := handler.findRouter(\"/user\")\n\tassert.True(t, found)\n\t_, found = handler.findRouter(\"/user/profile\")\n\tassert.False(t, found)\n\n\thandler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\t_, found = handler.findRouter(\"/user/profile\")\n\tassert.True(t, found)\n\n\t_, found = handler.findRouter(\"/user\")\n\tassert.True(t, found)\n\n\thandler.Route(http.MethodPost, \"/order/detail\", func(c *Context) {})\n\t_, found = handler.findRouter(\"/order\")\n\tassert.False(t, found)\n\n\t_, found = handler.findRouter(\"/order/detail\")\n\tassert.True(t, found)\n\n\thandler.Route(http.MethodPost, \"/order\", func(c *Context) {})\n\t_, found = handler.findRouter(\"/order\")\n\tassert.True(t, found)\n}"
  },
  {
    "path": "pkg/v2/context.go",
    "content": "package webv2\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype Context struct {\n\tW http.ResponseWriter\n\tR *http.Request\n}\n\nfunc (c *Context) ReadJson(data interface{}) error {\n\tbody, err := io.ReadAll(c.R.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(body, data)\n}\nfunc (c *Context) OkJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusOK, data)\n}\n\nfunc (c *Context) SystemErrJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusInternalServerError, data)\n}\n\nfunc (c *Context) BadRequestJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusBadRequest, data)\n}\n\nfunc (c *Context) WriteJson(status int, data interface{}) error {\n\tbs, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = c.W.Write(bs)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.W.WriteHeader(status)\n\treturn nil\n}\n\nfunc NewContext(w http.ResponseWriter, r *http.Request) *Context {\n\treturn &Context{\n\t\tW: w,\n\t\tR: r,\n\t}\n}"
  },
  {
    "path": "pkg/v2/filter.go",
    "content": "package webv2\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype FilterBuilder func(next Filter) Filter\n\ntype Filter func(c *Context)\n\nfunc MetricFilterBuilder(next Filter) Filter {\n\treturn func(c *Context) {\n\t\t// 执行前的时间\n\t\tstartTime := time.Now().UnixNano()\n\t\tnext(c)\n\t\t// 执行后的时间\n\t\tendTime := time.Now().UnixNano()\n\t\tfmt.Printf(\"run time: %d \\n\", endTime-startTime)\n\t}\n}"
  },
  {
    "path": "pkg/v2/handler.go",
    "content": "package webv2\n\ntype Handler interface {\n\tServeHTTP(c *Context)\n\tRoutable\n}\n\ntype handlerFunc func(c *Context)"
  },
  {
    "path": "pkg/v2/map_router.go",
    "content": "package webv2\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n)\n\n// 一种常用的GO设计模式，\n// 用于确保HandlerBasedOnMap肯定实现了这个接口\nvar _ Handler = &HandlerBasedOnMap{}\n\n\ntype HandlerBasedOnMap struct {\n\thandlers sync.Map\n}\n\nfunc (h *HandlerBasedOnMap) ServeHTTP(c *Context) {\n\trequest := c.R\n\tkey := h.key(request.Method, request.URL.Path)\n\thandler, ok := h.handlers.Load(key)\n\tif !ok {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"not any router match\"))\n\t\treturn\n\t}\n\n\thandler.(handlerFunc)(c)\n}\n\nfunc (h *HandlerBasedOnMap) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\tkey := h.key(method, pattern)\n\th.handlers.Store(key, handlerFunc)\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnMap) key(method string,\n\tpath string) string {\n\treturn fmt.Sprintf(\"%s#%s\", method, path)\n}\n\nfunc NewHandlerBasedOnMap() *HandlerBasedOnMap {\n\treturn &HandlerBasedOnMap{}\n}\n"
  },
  {
    "path": "pkg/v2/server.go",
    "content": "package webv2\n\nimport (\n\t\"net/http\"\n)\n\n// Routable 可路由的\ntype Routable interface {\n\t// Route 设定一个路由，命中该路由的会执行handlerFunc的代码\n\tRoute(method string, pattern string, handlerFunc handlerFunc) error\n}\n\n// Server 是http server 的顶级抽象\ntype Server interface {\n\tRoutable\n\t// Start 启动我们的服务器\n\tStart(address string) error\n}\n\n// sdkHttpServer 这个是基于 net/http 这个包实现的 http server\ntype sdkHttpServer struct {\n\t// Name server 的名字，给个标记，日志输出的时候用得上\n\tName    string\n\thandler Handler\n\troot    Filter\n}\n\nfunc (s *sdkHttpServer) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\treturn s.handler.Route(method, pattern, handlerFunc)\n}\n\nfunc (s *sdkHttpServer) Start(address string) error {\n\treturn http.ListenAndServe(address, s)\n}\n\nfunc (s *sdkHttpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request)  {\n\tc := NewContext(writer, request)\n\ts.root(c)\n}\n\nfunc NewSdkHttpServer(name string, builders ...FilterBuilder) Server {\n\n\t// 改用我们的树\n\thandler := NewHandlerBasedOnTree()\n\t//handler := NewHandlerBasedOnMap()\n\t// 因为我们是一个链，所以我们把最后的业务逻辑处理，也作为一环\n\tvar root Filter = handler.ServeHTTP\n\t// 从后往前把filter串起来\n\tfor i := len(builders) - 1; i >= 0; i-- {\n\t\tb := builders[i]\n\t\troot = b(root)\n\t}\n\tres := &sdkHttpServer{\n\t\tName: name,\n\t\thandler: handler,\n\t\troot: root,\n\t}\n\treturn res\n}\n\n"
  },
  {
    "path": "pkg/v2/tree_router.go",
    "content": "package webv2\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n)\n\nvar ErrorInvalidRouterPattern = errors.New(\"invalid router pattern\")\n\ntype HandlerBasedOnTree struct {\n\troot *node\n\n}\nvar supportMethods = [4]string {http.MethodPost, http.MethodGet,\n\thttp.MethodDelete, http.MethodPut}\nfunc NewHandlerBasedOnTree() Handler {\n\troot := &node{\n\t}\n\treturn &HandlerBasedOnTree{\n\t\troot: root,\n\t}\n}\n\n// ServeHTTP 就是从树里面找节点\n// 找到了就执行\nfunc (h *HandlerBasedOnTree) ServeHTTP(c *Context) {\n\thandler, found := h.findRouter(c.R.URL.Path)\n\tif !found {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n\t\treturn\n\t}\n\thandler(c)\n}\n\n// 这个是不好测试的版本，可以尝试为这个写单元测试\n// 会发现很难构造 request，也很难对 ResponseWriter做断言\n//func (h *HandlerBasedOnTree) ServeHTTP(c *Context) {\n//\turl := strings.Trim(c.R.URL.Path, \"/\")\n//\tpaths := strings.Split(url, \"/\")\n//\tcur := h.root\n//\tfor _, path := range paths {\n//\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n//\t\tmatchChild, found := h.findMatchChild(cur, path)\n//\t\tif !found {\n//\t\t\t// 找不到匹配的路径，直接返回\n//\t\t\tc.W.WriteHeader(404)\n//\t\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n//\t\t\treturn\n//\t\t}\n//\t\tcur = matchChild\n//\t}\n//\t// 到这里，应该是找完了\n//\tif cur.handler == nil {\n//\t\t// 到达这里是因为这种场景\n//\t\t// 比如说你注册了 /user/profile\n//\t\t// 然后你访问 /user\n//\t\tc.W.WriteHeader(404)\n//\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n//\t\treturn\n//\t}\n//\tcur.handler(c)\n//}\n\nfunc (h *HandlerBasedOnTree) findRouter(path string) (handlerFunc, bool) {\n\t// 去除头尾可能有的/，然后按照/切割成段\n\tpaths := strings.Split(strings.Trim(path, \"/\"), \"/\")\n\tcur := h.root\n\tfor _, p := range paths {\n\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, p)\n\t\tif !found {\n\t\t\treturn nil, false\n\t\t}\n\t\tcur = matchChild\n\t}\n\t// 到这里，应该是找完了\n\tif cur.handler == nil {\n\t\t// 到达这里是因为这种场景\n\t\t// 比如说你注册了 /user/profile\n\t\t// 然后你访问 /user\n\t\treturn nil, false\n\t}\n\treturn cur.handler, true\n}\n\n// Route 就相当于往树里面插入节点\nfunc (h *HandlerBasedOnTree) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\n\terr := h.validatePattern(pattern)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 将pattern按照URL的分隔符切割\n\t// 例如，/user/friends 将变成 [user, friends]\n\t// 将前后的/去掉，统一格式\n\tpattern = strings.Trim(pattern, \"/\")\n\tpaths := strings.Split(pattern, \"/\")\n\t// 当前指向根节点\n\tcur := h.root\n\tfor index, path := range paths {\n\n\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, path)\n\t\tif found {\n\t\t\tcur = matchChild\n\t\t} else {\n\t\t\t// 为当前节点根据\n\t\t\th.createSubTree(cur, paths[index:], handlerFunc)\n\t\t\treturn nil\n\t\t}\n\t}\n\t// 离开了循环，说明我们加入的是短路径，\n\t// 比如说我们先加入了 /order/detail\n\t// 再加入/order，那么会走到这里\n\tcur.handler = handlerFunc\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnTree) validatePattern(pattern string) error {\n\t// 校验 *，如果存在，必须在最后一个，并且它前面必须是/\n\t// 即我们只接受 /* 的存在，abc*这种是非法\n\n\tpos := strings.Index(pattern, \"*\")\n\t// 找到了 *\n\tif pos > 0 {\n\t\t// 必须是最后一个\n\t\tif pos != len(pattern) - 1 {\n\t\t\treturn ErrorInvalidRouterPattern\n\t\t}\n\t\tif pattern[pos-1] != '/' {\n\t\t\treturn ErrorInvalidRouterPattern\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnTree) findMatchChild(root *node, path string) (*node, bool) {\n\tvar wildcardNode *node\n\tfor _, child := range root.children {\n\t\t// 并不是 * 的节点命中了，直接返回\n\t\t// != * 是为了防止用户乱输入\n\t\tif child.path == path &&\n\t\t\tchild.path != \"*\"{\n\t\t\treturn child, true\n\t\t}\n\t\t// 命中了通配符的，我们看看后面还有没有更加详细的\n\t\tif child.path == \"*\" {\n\t\t\twildcardNode = child\n\t\t}\n\t}\n\treturn wildcardNode, wildcardNode != nil\n}\n\nfunc (h *HandlerBasedOnTree) createSubTree(root *node, paths []string, handlerFn handlerFunc) {\n\tcur := root\n\tfor _, path := range paths {\n\t\tnn := newNode(path)\n\t\tcur.children = append(cur.children, nn)\n\t\tcur = nn\n\t}\n\tcur.handler = handlerFn\n}\n\ntype node struct {\n\tpath string\n\tchildren []*node\n\n\t// 如果这是叶子节点，\n\t// 那么匹配上之后就可以调用该方法\n\thandler handlerFunc\n}\n\nfunc newNode(path string) *node {\n\treturn &node{\n\t\tpath: path,\n\t\tchildren: make([]*node, 0, 2),\n\t}\n}\n"
  },
  {
    "path": "pkg/v2/tree_router_test.go",
    "content": "package webv2\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestHandlerBasedOnTree_Route(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\tassert.NotNil(t, handler.root)\n\n\terr := handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tassert.Nil(t, err)\n\t// 开始做断言，这个时候我们应该确认，在根节点之下只有一个user节点\n\tassert.Equal(t, 1, len(handler.root.children))\n\n\tn := handler.root.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.path)\n\tassert.NotNil(t, n.handler)\n\tassert.Empty(t, n.children)\n\n\t// 我们只有\n\t//  user -> profile\n\terr = handler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(n.children))\n\tprofileNode := n.children[0]\n\tassert.NotNil(t, profileNode)\n\tassert.Equal(t, \"profile\", profileNode.path)\n\tassert.NotNil(t, profileNode.handler)\n\tassert.Empty(t, profileNode.children)\n\n\t// 试试重复\n\terr = handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tn = handler.root.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.path)\n\tassert.NotNil(t, n.handler)\n\t// 有profile节点\n\tassert.Equal(t, 1, len(n.children))\n\n\t// 给 user 再加一个节点\n\terr = handler.Route(http.MethodPost, \"/user/home\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(n.children))\n\thomeNode := n.children[1]\n\tassert.NotNil(t, homeNode)\n\tassert.Equal(t, \"home\", homeNode.path)\n\tassert.NotNil(t, homeNode.handler)\n\tassert.Empty(t, homeNode.children)\n\n\t// 添加 /order/detail\n\terr = handler.Route(http.MethodPost, \"/order/detail\", func(c *Context) {})\n\tassert.Equal(t, 2, len(handler.root.children))\n\torderNode := handler.root.children[1]\n\tassert.NotNil(t, orderNode)\n\tassert.Equal(t, \"order\", orderNode.path)\n\t// 此刻我们只有/order/detail，但是没有/order\n\tassert.Nil(t, orderNode.handler)\n\tassert.Equal(t, 1, len(orderNode.children))\n\n\torderDetailNode := orderNode.children[0]\n\tassert.NotNil(t, orderDetailNode)\n\tassert.Empty(t, orderDetailNode.children)\n\tassert.Equal(t, \"detail\", orderDetailNode.path)\n\tassert.NotNil(t, orderDetailNode.handler)\n\n\t// 加一个 /order\n\terr = handler.Route(http.MethodPost, \"/order\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(handler.root.children))\n\torderNode = handler.root.children[1]\n\tassert.Equal(t, \"order\", orderNode.path)\n\t// 此时我们有了 /order\n\tassert.NotNil(t, orderNode.handler)\n\n\terr = handler.Route(http.MethodPost, \"/order/*\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(orderNode.children))\n\torderWildcard := orderNode.children[1]\n\tassert.NotNil(t, orderWildcard)\n\tassert.NotNil(t, orderWildcard.handler)\n\tassert.Equal(t, \"*\", orderWildcard.path)\n\n\terr = handler.Route(http.MethodPost, \"/order/*/checkout\", func(c *Context) {})\n\tassert.NotNil(t, err)\n}\n\nfunc TestHandlerBasedOnTree_findRouter(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\t_ = handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tfn, found := handler.findRouter(\"/user\")\n\tassert.True(t, found)\n\tassert.NotNil(t, fn)\n\t_, found = handler.findRouter(\"/user/profile\")\n\tassert.False(t, found)\n\n\t_ = handler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\t_, found = handler.findRouter(\"/user/profile\")\n\tassert.True(t, found)\n\n\t_, found = handler.findRouter(\"/user\")\n\tassert.True(t, found)\n\n\tvar detailHandler handlerFunc = func(c *Context) {}\n\t_ = handler.Route(http.MethodPost, \"/order/detail\", detailHandler)\n\t_, found = handler.findRouter(\"/order\")\n\tassert.False(t, found)\n\n\tfn, found = handler.findRouter(\"/order/detail\")\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(detailHandler, fn))\n\n\tvar wildcardHandler handlerFunc = func(c *Context) {}\n\t_ = handler.Route(http.MethodPost, \"/order/*\", wildcardHandler)\n\t_, found = handler.findRouter(\"/order\")\n\tassert.False(t, found)\n\n\tfn, found = handler.findRouter(\"/order/detail\")\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(detailHandler, fn))\n\n\tfn, found = handler.findRouter(\"/order/checkout\")\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(wildcardHandler, fn))\n}\n\nfunc handlerFuncEquals(hf1 handlerFunc, hf2 handlerFunc) bool {\n\treturn reflect.ValueOf(hf1).Pointer() == reflect.ValueOf(hf2).Pointer()\n}"
  },
  {
    "path": "pkg/v3/context.go",
    "content": "package webv3\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype Context struct {\n\tW http.ResponseWriter\n\tR *http.Request\n\tPathParams map[string]string\n}\n\nfunc (c *Context) ReadJson(data interface{}) error {\n\tbody, err := io.ReadAll(c.R.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(body, data)\n}\nfunc (c *Context) OkJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusOK, data)\n}\n\nfunc (c *Context) SystemErrJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusInternalServerError, data)\n}\n\nfunc (c *Context) BadRequestJson(data interface{}) error {\n\t// http 库里面提前定义好了各种响应码\n\treturn c.WriteJson(http.StatusBadRequest, data)\n}\n\nfunc (c *Context) WriteJson(status int, data interface{}) error {\n\tc.W.WriteHeader(status)\n\tbs, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = c.W.Write(bs)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc NewContext(w http.ResponseWriter, r *http.Request) *Context {\n\treturn &Context{\n\t\tW: w,\n\t\tR: r,\n\t\t// 一般路径参数都是一个，所以容量1就可以了\n\t\tPathParams: make(map[string]string, 1),\n\t}\n}"
  },
  {
    "path": "pkg/v3/filter.go",
    "content": "package webv3\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype FilterBuilder func(next Filter) Filter\n\ntype Filter func(c *Context)\n\nfunc MetricFilterBuilder(next Filter) Filter {\n\treturn func(c *Context) {\n\t\t// 执行前的时间\n\t\tstartTime := time.Now().UnixNano()\n\t\tnext(c)\n\t\t// 执行后的时间\n\t\tendTime := time.Now().UnixNano()\n\t\tfmt.Printf(\"run time: %d \\n\", endTime-startTime)\n\t}\n}"
  },
  {
    "path": "pkg/v3/handler.go",
    "content": "package webv3\n\ntype Handler interface {\n\tServeHTTP(c *Context)\n\tRoutable\n}\n\ntype handlerFunc func(c *Context)"
  },
  {
    "path": "pkg/v3/map_router.go",
    "content": "package webv3\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n)\n\n// 一种常用的GO设计模式，\n// 用于确保HandlerBasedOnMap肯定实现了这个接口\nvar _ Handler = &HandlerBasedOnMap{}\n\n\ntype HandlerBasedOnMap struct {\n\thandlers sync.Map\n}\n\nfunc (h *HandlerBasedOnMap) ServeHTTP(c *Context) {\n\trequest := c.R\n\tkey := h.key(request.Method, request.URL.Path)\n\thandler, ok := h.handlers.Load(key)\n\tif !ok {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"not any router match\"))\n\t\treturn\n\t}\n\n\thandler.(handlerFunc)(c)\n}\n\nfunc (h *HandlerBasedOnMap) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\tkey := h.key(method, pattern)\n\th.handlers.Store(key, handlerFunc)\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnMap) key(method string,\n\tpath string) string {\n\treturn fmt.Sprintf(\"%s#%s\", method, path)\n}\n\nfunc NewHandlerBasedOnMap() *HandlerBasedOnMap {\n\treturn &HandlerBasedOnMap{}\n}\n"
  },
  {
    "path": "pkg/v3/server.go",
    "content": "package webv3\n\nimport (\n\t\"net/http\"\n)\n\n// Routable 可路由的\ntype Routable interface {\n\t// Route 设定一个路由，命中该路由的会执行handlerFunc的代码\n\tRoute(method string, pattern string, handlerFunc handlerFunc) error\n}\n\n// Server 是http server 的顶级抽象\ntype Server interface {\n\tRoutable\n\t// Start 启动我们的服务器\n\tStart(address string) error\n}\n\n// sdkHttpServer 这个是基于 net/http 这个包实现的 http server\ntype sdkHttpServer struct {\n\t// Name server 的名字，给个标记，日志输出的时候用得上\n\tName    string\n\thandler Handler\n\troot    Filter\n}\n\nfunc (s *sdkHttpServer) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\treturn s.handler.Route(method, pattern, handlerFunc)\n}\n\nfunc (s *sdkHttpServer) Start(address string) error {\n\treturn http.ListenAndServe(address, s)\n}\n\nfunc (s *sdkHttpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request)  {\n\tc := NewContext(writer, request)\n\ts.root(c)\n}\n\nfunc NewSdkHttpServer(name string, builders ...FilterBuilder) Server {\n\n\t// 改用我们的树\n\thandler := NewHandlerBasedOnTree()\n\t//handler := NewHandlerBasedOnMap()\n\t// 因为我们是一个链，所以我们把最后的业务逻辑处理，也作为一环\n\tvar root Filter = handler.ServeHTTP\n\t// 从后往前把filter串起来\n\tfor i := len(builders) - 1; i >= 0; i-- {\n\t\tb := builders[i]\n\t\troot = b(root)\n\t}\n\tres := &sdkHttpServer{\n\t\tName: name,\n\t\thandler: handler,\n\t\troot: root,\n\t}\n\treturn res\n}\n\n"
  },
  {
    "path": "pkg/v3/tree_node.go",
    "content": "package webv3\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\n\t// 根节点，只有根用这个\n\tnodeTypeRoot = iota\n\n\t// *\n\tnodeTypeAny\n\n\t// 路径参数\n\tnodeTypeParam\n\n\t// 正则\n\tnodeTypeReg\n\n\t// 静态，即完全匹配\n\tnodeTypeStatic\n)\n\nconst any = \"*\"\n\n// matchFunc 承担两个职责，一个是判断是否匹配，一个是在匹配之后\n// 将必要的数据写入到 Context\n// 所谓必要的数据，这里基本上是指路径参数\ntype matchFunc func(path string, c *Context) bool\n\ntype node struct {\n\tchildren []*node\n\n\t// 如果这是叶子节点，\n\t// 那么匹配上之后就可以调用该方法\n\thandler   handlerFunc\n\tmatchFunc matchFunc\n\n\t// 原始的 pattern。注意，它不是完整的pattern，\n\t// 而是匹配到这个节点的pattern\n\tpattern string\n\tnodeType int\n}\n\n// 静态节点\nfunc newStaticNode(path string) *node {\n\treturn &node{\n\t\tchildren: make([]*node, 0, 2),\n\t\tmatchFunc: func(p string, c *Context) bool {\n\t\t\treturn path == p && p != \"*\"\n\t\t},\n\t\tnodeType: nodeTypeStatic,\n\t\tpattern:  path,\n\t}\n}\n\n\nfunc newRootNode(method string) *node {\n\treturn &node{\n\t\tchildren: make([]*node, 0, 2),\n\t\tmatchFunc: func( p string, c *Context) bool {\n\t\t\tpanic(\"never call me\")\n\t\t},\n\t\tnodeType: nodeTypeRoot,\n\t\tpattern:  method,\n\t}\n}\n\nfunc newNode(path string) *node {\n\tif path == \"*\"{\n\t\treturn newAnyNode()\n\t}\n\tif strings.HasPrefix(path, \":\") {\n\t\treturn newParamNode(path)\n\t}\n\treturn newStaticNode(path)\n}\n\n// 通配符 * 节点\nfunc newAnyNode() *node {\n\treturn &node{\n\t\t// 因为我们不允许 * 后面还有节点，所以这里可以不用初始化\n\t\t//children: make([]*node, 0, 2),\n\t\tmatchFunc: func(p string, c *Context) bool {\n\t\t\treturn true\n\t\t},\n\t\tnodeType: nodeTypeAny,\n\t\tpattern:  any,\n\t}\n}\n\n// 路径参数节点\nfunc newParamNode(path string) *node {\n\tparamName := path[1:]\n\treturn &node{\n\t\tchildren: make([]*node, 0, 2),\n\t\tmatchFunc: func(p string, c *Context) bool {\n\t\t\tif c != nil {\n\t\t\t\tc.PathParams[paramName] = p\n\t\t\t}\n\t\t\t// 如果自身是一个参数路由，\n\t\t\t// 然后又来一个通配符，我们认为是不匹配的\n\t\t\treturn p != any\n\t\t},\n\t\tnodeType: nodeTypeParam,\n\t\tpattern:  path,\n\t}\n}\n\n// 正则节点\n//func newRegNode(path string) *node {\n//\t// 依据你的规则拿到正则表达式\n//\treturn &node{\n//\t\tchildren: make([]*node, 0, 2),\n//\t\tmatchFunc: func(p string, c *Context) bool {\n//\t\t\t// 怎么写？\n//\t\t},\n//\t\tnodeType: nodeTypeParam,\n//\t\tpattern: path,\n//\t}\n//}\n\n"
  },
  {
    "path": "pkg/v3/tree_router.go",
    "content": "package webv3\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n)\n\nvar ErrorInvalidRouterPattern = errors.New(\"invalid router pattern\")\nvar ErrorInvalidMethod = errors.New(\"invalid method\")\n\ntype HandlerBasedOnTree struct {\n\tforest map[string]*node\n}\n\nvar supportMethods = [4]string {\n\thttp.MethodGet,\n\thttp.MethodPost,\n\thttp.MethodPut,\n\thttp.MethodDelete,\n}\n\nfunc NewHandlerBasedOnTree() Handler {\n\tforest := make(map[string]*node, len(supportMethods))\n\tfor _, m :=range supportMethods {\n\t\tforest[m] = newRootNode(m)\n\t}\n\treturn &HandlerBasedOnTree{\n\t\tforest: forest,\n\t}\n}\n\n// ServeHTTP 就是从树里面找节点\n// 找到了就执行\nfunc (h *HandlerBasedOnTree) ServeHTTP(c *Context) {\n\thandler, found := h.findRouter(c.R.Method, c.R.URL.Path, c)\n\tif !found {\n\t\tc.W.WriteHeader(http.StatusNotFound)\n\t\t_, _ = c.W.Write([]byte(\"Not Found\"))\n\t\treturn\n\t}\n\thandler(c)\n}\n\nfunc (h *HandlerBasedOnTree) findRouter(method, path string, c *Context) (handlerFunc, bool) {\n\t// 去除头尾可能有的/，然后按照/切割成段\n\tpaths := strings.Split(strings.Trim(path, \"/\"), \"/\")\n\tcur, ok := h.forest[method]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\tfor _, p := range paths {\n\t\t// 从子节点里边找一个匹配到了当前 p 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, p, c)\n\t\tif !found {\n\t\t\treturn nil, false\n\t\t}\n\t\tcur = matchChild\n\t}\n\t// 到这里，应该是找完了\n\tif cur.handler == nil {\n\t\t// 到达这里是因为这种场景\n\t\t// 比如说你注册了 /user/profile\n\t\t// 然后你访问 /user\n\t\treturn nil, false\n\t}\n\treturn cur.handler, true\n}\n\n// Route 就相当于往树里面插入节点\nfunc (h *HandlerBasedOnTree) Route(method string, pattern string,\n\thandlerFunc handlerFunc) error {\n\n\terr := h.validatePattern(pattern)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 将pattern按照URL的分隔符切割\n\t// 例如，/user/friends 将变成 [user, friends]\n\t// 将前后的/去掉，统一格式\n\tpattern = strings.Trim(pattern, \"/\")\n\tpaths := strings.Split(pattern, \"/\")\n\n\t// 当前指向根节点\n\tcur, ok := h.forest[method]\n\tif !ok {\n\t\treturn ErrorInvalidMethod\n\t}\n\tfor index, path := range paths {\n\n\t\t// 从子节点里边找一个匹配到了当前 path 的节点\n\t\tmatchChild, found := h.findMatchChild(cur, path, nil)\n\t\t// != nodeTypeAny 是考虑到 /order/* 和 /order/:id 这种注册顺序\n\t\tif found && matchChild.nodeType != nodeTypeAny {\n\t\t\tcur = matchChild\n\t\t} else {\n\t\t\t// 为当前节点根据\n\t\t\th.createSubTree(cur, paths[index:], handlerFunc)\n\t\t\treturn nil\n\t\t}\n\t}\n\t// 离开了循环，说明我们加入的是短路径，\n\t// 比如说我们先加入了 /order/detail\n\t// 再加入/order，那么会走到这里\n\tcur.handler = handlerFunc\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnTree) validatePattern(pattern string) error {\n\t// 校验 *，如果存在，必须在最后一个，并且它前面必须是/\n\t// 即我们只接受 /* 的存在，abc*这种是非法\n\n\tpos := strings.Index(pattern, \"*\")\n\t// 找到了 *\n\tif pos > 0 {\n\t\t// 必须是最后一个\n\t\tif pos != len(pattern) - 1 {\n\t\t\treturn ErrorInvalidRouterPattern\n\t\t}\n\t\tif pattern[pos-1] != '/' {\n\t\t\treturn ErrorInvalidRouterPattern\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *HandlerBasedOnTree) findMatchChild(root *node,\n\tpath string, c *Context) (*node, bool) {\n\tcandidates := make([]*node, 0, 2)\n\tfor _, child := range root.children {\n\t\tif child.matchFunc(path, c) {\n\t\t\tcandidates = append(candidates, child)\n\t\t}\n\t}\n\n\tif len(candidates) == 0 {\n\t\treturn nil, false\n\t}\n\n\t// type 也决定了它们的优先级\n\tsort.Slice(candidates, func(i, j int) bool {\n\t\treturn candidates[i].nodeType < candidates[j].nodeType\n\t})\n\treturn candidates[len(candidates) - 1], true\n}\n\nfunc (h *HandlerBasedOnTree) createSubTree(root *node, paths []string, handlerFn handlerFunc) {\n\tcur := root\n\tfor _, path := range paths {\n\t\tnn := newNode(path)\n\t\tcur.children = append(cur.children, nn)\n\t\tcur = nn\n\t}\n\tcur.handler = handlerFn\n}\n\n"
  },
  {
    "path": "pkg/v3/tree_router_test.go",
    "content": "package webv3\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestHandlerBasedOnTree_Route(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\t// 要确认已经为支持的方法创建了节点\n\tassert.Equal(t, len(supportMethods), len(handler.forest))\n\n\tpostNode := handler.forest[http.MethodPost]\n\n\terr := handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(postNode.children))\n\n\tn := postNode.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.pattern)\n\tassert.NotNil(t, n.handler)\n\tassert.Empty(t, n.children)\n\n\t// 我们只有\n\t//  user -> profile\n\terr = handler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(n.children))\n\tprofileNode := n.children[0]\n\tassert.NotNil(t, profileNode)\n\tassert.Equal(t, \"profile\", profileNode.pattern)\n\tassert.NotNil(t, profileNode.handler)\n\tassert.Empty(t, profileNode.children)\n\n\t// 试试重复\n\terr = handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tn = postNode.children[0]\n\tassert.NotNil(t, n)\n\tassert.Equal(t, \"user\", n.pattern)\n\tassert.NotNil(t, n.handler)\n\t// 有profile节点\n\tassert.Equal(t, 1, len(n.children))\n\n\t// 给 user 再加一个节点\n\terr = handler.Route(http.MethodPost, \"/user/home\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(n.children))\n\thomeNode := n.children[1]\n\tassert.NotNil(t, homeNode)\n\tassert.Equal(t, \"home\", homeNode.pattern)\n\tassert.NotNil(t, homeNode.handler)\n\tassert.Empty(t, homeNode.children)\n\n\t// 添加 /order/detail\n\terr = handler.Route(http.MethodPost, \"/order/detail\", func(c *Context) {})\n\tassert.Equal(t, 2, len(postNode.children))\n\torderNode := postNode.children[1]\n\tassert.NotNil(t, orderNode)\n\tassert.Equal(t, \"order\", orderNode.pattern)\n\t// 此刻我们只有/order/detail，但是没有/order\n\tassert.Nil(t, orderNode.handler)\n\tassert.Equal(t, 1, len(orderNode.children))\n\n\torderDetailNode := orderNode.children[0]\n\tassert.NotNil(t, orderDetailNode)\n\tassert.Empty(t, orderDetailNode.children)\n\tassert.Equal(t, \"detail\", orderDetailNode.pattern)\n\tassert.NotNil(t, orderDetailNode.handler)\n\n\t// 加一个 /order\n\terr = handler.Route(http.MethodPost, \"/order\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(postNode.children))\n\torderNode = postNode.children[1]\n\tassert.Equal(t, \"order\", orderNode.pattern)\n\t// 此时我们有了 /order\n\tassert.NotNil(t, orderNode.handler)\n\n\terr = handler.Route(http.MethodPost, \"/order/*\", func(c *Context) {})\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(orderNode.children))\n\torderWildcard := orderNode.children[1]\n\tassert.NotNil(t, orderWildcard)\n\tassert.NotNil(t, orderWildcard.handler)\n\tassert.Equal(t, \"*\", orderWildcard.pattern)\n\n\terr = handler.Route(http.MethodPost, \"/order/*/checkout\", func(c *Context) {})\n\tassert.Equal(t, ErrorInvalidRouterPattern, err)\n\n\terr = handler.Route(http.MethodConnect, \"/order/checkout\", func(c *Context) {})\n\tassert.Equal(t, ErrorInvalidMethod, err)\n\n\terr = handler.Route(http.MethodPost, \"/order/:id\", func(c *Context){})\n\tassert.Nil(t, err)\n\t// 这时候我们有/order/* 和 /order/:id\n\t// 因为我们并没有认为它们不兼容，而是/order/:id优先\n\tassert.Equal(t, 3, len(orderNode.children))\n\torderParamNode := orderNode.children[2]\n\tassert.Equal(t, \":id\", orderParamNode.pattern)\n\n}\n\nfunc TestHandlerBasedOnTree_findRouter(t *testing.T) {\n\thandler := NewHandlerBasedOnTree().(*HandlerBasedOnTree)\n\t_ = handler.Route(http.MethodPost, \"/user\", func(c *Context) {})\n\tctx := NewContext(nil, nil)\n\tfn, found := handler.findRouter(http.MethodPost, \"/user\", ctx)\n\tassert.True(t, found)\n\tassert.NotNil(t, fn)\n\t_, found = handler.findRouter(http.MethodPost,\"/user/profile\", ctx)\n\tassert.False(t, found)\n\n\t_ = handler.Route(http.MethodPost, \"/user/profile\", func(c *Context) {})\n\t_, found = handler.findRouter(http.MethodPost, \"/user/profile\", ctx)\n\tassert.True(t, found)\n\n\t_, found = handler.findRouter(http.MethodPost, \"/user\", ctx)\n\tassert.True(t, found)\n\n\tvar detailHandler handlerFunc = func(c *Context) {}\n\t_ = handler.Route(http.MethodPost, \"/order/detail\", detailHandler)\n\t_, found = handler.findRouter(http.MethodPost,\"/order\", ctx)\n\tassert.False(t, found)\n\n\tfn, found = handler.findRouter(http.MethodPost,\"/order/detail\", ctx)\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(detailHandler, fn))\n\n\tvar wildcardHandler handlerFunc = func(c *Context) {}\n\t_ = handler.Route(http.MethodPost, \"/order/*\", wildcardHandler)\n\t_, found = handler.findRouter(http.MethodPost,\"/order\", ctx)\n\tassert.False(t, found)\n\n\tfn, found = handler.findRouter(http.MethodPost,\"/order/detail\", ctx)\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(detailHandler, fn))\n\n\tfn, found = handler.findRouter(http.MethodPost,\"/order/checkout\", ctx)\n\tassert.True(t, found)\n\tassert.True(t, handlerFuncEquals(wildcardHandler, fn))\n\n\t_, found = handler.findRouter(http.MethodGet,\"/order/checkout\", ctx)\n\tassert.False(t, found)\n\n\t// 参数路由\n\thandler.Route(http.MethodPost, \"/order/*\", wildcardHandler)\n}\n\nfunc handlerFuncEquals(hf1 handlerFunc, hf2 handlerFunc) bool {\n\treturn reflect.ValueOf(hf1).Pointer() == reflect.ValueOf(hf2).Pointer()\n}"
  }
]