move gee day1-day7 to gee-web

This commit is contained in:
gzdaijie
2020-01-18 15:50:29 +08:00
parent cb92a9a563
commit c4f2bb9911
69 changed files with 255 additions and 241 deletions
+14 -215
View File
@@ -1,222 +1,21 @@
# 7天用Go从零实现Web框架Gee
# 7天用Go从零实现系列
![Gee](doc/gee/gee.jpg)
## 序言
7天能写什么呢?类似 gin 的 web 框架?类似 groupcache 的分布式缓存?或者一个简单的 Python 解释器?希望这个仓库能给你答案。
如果是 Go 语言的初学者,推荐先阅读 [Go 语言简明教程](https://geektutu.com/post/quick-golang.html)
推荐先阅读 **[Go 语言简明教程](https://geektutu.com/post/quick-golang.html)**,一篇文章了解Go的基本语法、并发编程,依赖管理等内容
Gee 的设计与实现参考了Gin,这个教程可以快速入门:[Go Gin简明教程](https://geektutu.com/post/quick-go-gin.html)。
## 7天用Go从零实现Web框架Gee
## [教程目录](https://geektutu.com/post/gee.html)
Gee 的设计与实现参考了Gin[Go Gin简明教程](https://geektutu.com/post/quick-go-gin.html)可以快速入门。
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](day1-http-base)
- [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html)[Code - Github](day2-context)
- [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html)[Code - Github](day3-router)
- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html)[Code - Github](day4-group)
- [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html)[Code - Github](day5-middleware)
- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html)[Code - Github](day6-template)
- [第七天:错误恢复(Panic Recover)](https://geektutu.com/post/gee-day7.html)[Code - Github](day7-panic-recover)
#### [教程目录](https://geektutu.com/post/gee.html)
## Day 1 - Static Route
```go
func main() {
r := gee.New()
r.GET("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
})
r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
})
r.Run(":9999")
}
```
## Day 2 - Context Design
```go
func main() {
r := gee.New()
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
r.GET("/hello", func(c *gee.Context) {
// expect /hello?name=geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
r.POST("/login", func(c *gee.Context) {
c.JSON(http.StatusOK, &map[string]string{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
})
r.Run(":9999")
}
```
## Day 3 - Dynamic Route
```go
func main() {
r := gee.New()
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
r.GET("/hello", func(c *gee.Context) {
// expect /hello?name=geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
r.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
r.GET("/assets/*filepath", func(c *gee.Context) {
c.JSON(http.StatusOK, gee.H{"filepath": c.Param("filepath")})
})
r.Run(":9999")
}
```
## Day 4 - Nesting Group Control
```go
func main() {
r := gee.New()
v1 := r.Group("/v1")
{
v1.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
v1.GET("/hello", func(c *gee.Context) {
// expect /hello?name=geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
}
v2 := r.Group("/v2")
{
v2.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
v2.POST("/login", func(c *gee.Context) {
c.JSON(http.StatusOK, &map[string]string{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
})
}
r.Run(":9999")
}
```
## Day 5 - Middleware
```go
func onlyForV2() gee.HandlerFunc {
return func(c *gee.Context) {
// Start timer
t := time.Now()
// if a server error occurred
c.Fail(500, "Internal Server Error")
// Calculate resolution time
log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t))
}
}
func main() {
r := gee.New()
r.Use(gee.Logger()) // global midlleware
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
v2 := r.Group("/v2")
v2.Use(onlyForV2()) // v2 group middleware
{
v2.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
}
r.Run(":9999")
}
```
## Day 6 - HTML Template
```go
type student struct {
Name string
Age int8
}
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}
func main() {
r := gee.New()
r.Use(gee.Logger())
r.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
r.LoadHTMLGlob("templates/*")
r.Static("/assets", "./static")
stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "css.tmpl", nil)
})
r.GET("/students", func(c *gee.Context) {
c.HTML(http.StatusOK, "arr.tmpl", gee.H{
"title": "gee",
"stuArr": [2]*student{stu1, stu2},
})
})
r.GET("/date", func(c *gee.Context) {
c.HTML(http.StatusOK, "custom_func.tmpl", gee.H{
"title": "gee",
"now": time.Date(2019, 8, 17, 0, 0, 0, 0, time.UTC),
})
})
r.Run(":9999")
}
```
## Day 7 - Panic Recover
```go
func main() {
r := gee.Default()
r.GET("/", func(c *gee.Context) {
c.String(http.StatusOK, "Hello Geektutu\n")
})
// index out of range for testing Recovery()
r.GET("/panic", func(c *gee.Context) {
names := []string{"geektutu"}
c.String(http.StatusOK, names[100])
})
r.Run(":9999")
}
```
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](gee-web/day1-http-base)
- [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html)[Code - Github](gee-web/day2-context)
- [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html)[Code - Github](gee-web/day3-router)
- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html)[Code - Github](gee-web/day4-group)
- [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html)[Code - Github](gee-web/day5-middleware)
- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html)[Code - Github](gee-web/day6-template)
- [第七天:错误恢复(Panic Recover)](https://geektutu.com/post/gee-day7.html)[Code - Github](gee-web/day7-panic-recover)
+215
View File
@@ -0,0 +1,215 @@
# 7天用Go从零实现Web框架Gee
![Gee](doc/gee/gee.jpg)
## 教程目录
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)
- [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html)
- [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html)
- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html)
- [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html)
- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html)
- [第七天:错误恢复(Panic Recover)](https://geektutu.com/post/gee-day7.html)
## Day 1 - Static Route
```go
func main() {
r := gee.New()
r.GET("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
})
r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
})
r.Run(":9999")
}
```
## Day 2 - Context Design
```go
func main() {
r := gee.New()
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
r.GET("/hello", func(c *gee.Context) {
// expect /hello?name=geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
r.POST("/login", func(c *gee.Context) {
c.JSON(http.StatusOK, &map[string]string{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
})
r.Run(":9999")
}
```
## Day 3 - Dynamic Route
```go
func main() {
r := gee.New()
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
r.GET("/hello", func(c *gee.Context) {
// expect /hello?name=geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
r.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
r.GET("/assets/*filepath", func(c *gee.Context) {
c.JSON(http.StatusOK, gee.H{"filepath": c.Param("filepath")})
})
r.Run(":9999")
}
```
## Day 4 - Nesting Group Control
```go
func main() {
r := gee.New()
v1 := r.Group("/v1")
{
v1.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
v1.GET("/hello", func(c *gee.Context) {
// expect /hello?name=geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
}
v2 := r.Group("/v2")
{
v2.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
v2.POST("/login", func(c *gee.Context) {
c.JSON(http.StatusOK, &map[string]string{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
})
}
r.Run(":9999")
}
```
## Day 5 - Middleware
```go
func onlyForV2() gee.HandlerFunc {
return func(c *gee.Context) {
// Start timer
t := time.Now()
// if a server error occurred
c.Fail(500, "Internal Server Error")
// Calculate resolution time
log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t))
}
}
func main() {
r := gee.New()
r.Use(gee.Logger()) // global midlleware
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
v2 := r.Group("/v2")
v2.Use(onlyForV2()) // v2 group middleware
{
v2.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
}
r.Run(":9999")
}
```
## Day 6 - HTML Template
```go
type student struct {
Name string
Age int8
}
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}
func main() {
r := gee.New()
r.Use(gee.Logger())
r.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
r.LoadHTMLGlob("templates/*")
r.Static("/assets", "./static")
stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "css.tmpl", nil)
})
r.GET("/students", func(c *gee.Context) {
c.HTML(http.StatusOK, "arr.tmpl", gee.H{
"title": "gee",
"stuArr": [2]*student{stu1, stu2},
})
})
r.GET("/date", func(c *gee.Context) {
c.HTML(http.StatusOK, "custom_func.tmpl", gee.H{
"title": "gee",
"now": time.Date(2019, 8, 17, 0, 0, 0, 0, time.UTC),
})
})
r.Run(":9999")
}
```
## Day 7 - Panic Recover
```go
func main() {
r := gee.Default()
r.GET("/", func(c *gee.Context) {
c.String(http.StatusOK, "Hello Geektutu\n")
})
// index out of range for testing Recovery()
r.GET("/panic", func(c *gee.Context) {
names := []string{"geektutu"}
c.String(http.StatusOK, names[100])
})
r.Run(":9999")
}
```
+4 -4
View File
@@ -24,7 +24,7 @@ github: https://github.com/geektutu/7days-golang
Go语言内置了 `net/http`库,封装了HTTP网络编程的基础的接口,我们实现的`Gee` Web 框架便是基于`net/http`的。我们接下来通过一个例子,简单介绍下这个库的使用。
**[day1-http-base/base1/main.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base1)**
**[day1-http-base/base1/main.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day1-http-base/base1)**
```go
package main
@@ -82,7 +82,7 @@ func ListenAndServe(address string, h Handler) error
第二个参数的类型是什么呢?通过查看`net/http`的源码可以发现,`Handler`是一个接口,需要实现方法 _ServeHTTP_ ,也就是说,只要传入任何实现了 _ServerHTTP_ 接口的实例,所有的HTTP请求,就都交给了该实例处理了。马上来试一试吧。
**[day1-http-base/base2/main.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base2)**
**[day1-http-base/base2/main.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day1-http-base/base2)**
```go
package main
@@ -135,7 +135,7 @@ main.go
### main.go
**[day1-http-base/base3/main.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base3)**
**[day1-http-base/base3/main.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day1-http-base/base3)**
```go
package main
@@ -167,7 +167,7 @@ func main() {
### gee.go
**[day1-http-base/base3/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base3)**
**[day1-http-base/base3/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day1-http-base/base3)**
```go
package gee
+4 -4
View File
@@ -25,7 +25,7 @@ github: https://github.com/geektutu/7days-golang
为了展示第二天的成果,我们看一看在使用时的效果。
[day2-context/main.go](https://github.com/geektutu/7days-golang/tree/master/day2-context)
[day2-context/main.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day2-context)
```go
@@ -90,7 +90,7 @@ c.JSON(http.StatusOK, gee.H{
### 具体实现
[day2-context/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/day2-context)
[day2-context/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day2-context)
```go
type H map[string]interface{}
@@ -168,7 +168,7 @@ func (c *Context) HTML(code int, html string) {
我们将和路由相关的方法和结构提取了出来,放到了一个新的文件中`router.go`,方便我们下一次对 router 的功能进行增强,例如提供动态路由的支持。 router 的 handle 方法作了一个细微的调整,即 handler 的参数,变成了 Context。
[day2-context/gee/router.go](https://github.com/geektutu/7days-golang/tree/master/day2-context)
[day2-context/gee/router.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day2-context)
```go
type router struct {
@@ -197,7 +197,7 @@ func (r *router) handle(c *Context) {
## 框架入口
[day2-context/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day2-context)
[day2-context/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day2-context)
```go
// HandlerFunc defines the request handler used by gee
+1 -1
View File
@@ -52,7 +52,7 @@ HTTP请求的路径恰好是由`/`分隔的多段构成的,因此,每一段
首先我们需要设计树节点上应该存储那些信息。
**[day3-router/gee/trie.go](https://github.com/geektutu/7days-golang/tree/master/day3-router/gee)**
**[day3-router/gee/trie.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day3-router/gee)**
```go
type node struct {

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+1 -1
View File
@@ -49,7 +49,7 @@ v1.GET("/", func(c *gee.Context) {
所以,最后的 Group 的定义是这样的:
**[day4-group/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day4-group/gee)**
**[day4-group/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day4-group/gee)**
```go
RouterGroup struct {

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+4 -4
View File
@@ -33,7 +33,7 @@ github: https://github.com/geektutu/7days-golang
Gee 的中间件的定义与路由映射的 Handler 一致,处理的输入是`Context`对象。插入点是框架接收到请求初始化`Context`对象后,允许用户使用自己定义的中间件做一些额外的处理,例如记录日志等,以及对`Context`进行二次加工。另外通过调用`(*Context).Next()`函数,中间件可等待用户自己定义的 `Handler`处理结束后,再做一些额外的操作,例如计算本次处理所用时间等。即 Gee 的中间件支持用户在请求被处理的前后,做一些额外的操作。举个例子,我们希望最终能够支持如下定义的中间件,`c.Next()`表示等待执行其他的中间件或用户的`Handler`
****[day4-group/gee/logger.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)****
****[day4-group/gee/logger.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day5-middleware/gee)****
```go
func Logger() HandlerFunc {
@@ -56,7 +56,7 @@ func Logger() HandlerFunc {
为此,我们给`Context`添加了2个参数,定义了`Next`方法:
**[day4-group/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)**
**[day4-group/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day5-middleware/gee)**
```go
type Context struct {
@@ -130,7 +130,7 @@ func B(c *Context) {
- 定义`Use`函数,将中间件应用到某个 Group 。
**[day4-group/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)**
**[day4-group/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day5-middleware/gee)**
```go
// Use is defined to add middleware to the group
@@ -155,7 +155,7 @@ ServeHTTP 函数也有变化,当我们接收到一个具体请求时,要判
- handle 函数中,将从路由匹配得到的 Handler 添加到 `c.handlers`列表中,执行`c.Next()`
**[day4-group/gee/router.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)**
**[day4-group/gee/router.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day5-middleware/gee)**
```go
func (r *router) handle(c *Context) {

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

+3 -3
View File
@@ -36,7 +36,7 @@ github: https://github.com/geektutu/7days-golang
找到文件后,如何返回这一步,`net/http`库已经实现了。因此,gee 框架要做的,仅仅是解析请求的地址,映射到服务器上文件的真实地址,交给`http.FileServer`处理就好了。
[day6-template/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day6-template/gee)
[day6-template/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day6-template/gee)
```go
// create static handler
@@ -103,7 +103,7 @@ func (engine *Engine) LoadHTMLGlob(pattern string) {
接下来,对原来的 `(*Context).HTML()`方法做了些小修改,使之支持根据模板文件名选择模板进行渲染。
[day6-template/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/day6-template/gee)
[day6-template/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day6-template/gee)
```go
func (c *Context) HTML(code int, name string, data interface{}) {
@@ -140,7 +140,7 @@ func (c *Context) HTML(code int, name string, data interface{}) {
</html>
```
[day6-template/main.go](https://github.com/geektutu/7days-golang/tree/master/day6-template/gee)
[day6-template/main.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day6-template/gee)
```go
type student struct {

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

+2 -2
View File
@@ -168,7 +168,7 @@ func Recovery() HandlerFunc {
你可能注意到,这里有一个 *trace()* 函数,这个函数是用来获取触发 panic 的堆栈信息,完整代码如下:
[day7-panic-recover/gee/recovery.go](https://github.com/geektutu/7days-golang/tree/master/day7-panic-recover)
[day7-panic-recover/gee/recovery.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day7-panic-recover)
```go
package gee
@@ -219,7 +219,7 @@ func Recovery() HandlerFunc {
## 使用 Demo
[day7-panic-recover/main.go](https://github.com/geektutu/7days-golang/tree/master/day7-panic-recover)
[day7-panic-recover/main.go](https://github.com/geektutu/7days-golang/tree/master/gee-web/day7-panic-recover)
```go
package main

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

+7 -7
View File
@@ -62,13 +62,13 @@ func handler(w http.ResponseWriter, r *http.Request) {
## 目录
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day1-http-base)
- [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day2-context)
- [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day3-router)
- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day4-group)
- [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day5-middleware)
- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day6-template)
- [第七天:错误恢复(Panic Recover)](https://geektutu.com/post/gee-day7.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day7-panic-recover)
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day1-http-base)
- [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day2-context)
- [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day3-router)
- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day4-group)
- [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day5-middleware)
- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day6-template)
- [第七天:错误恢复(Panic Recover)](https://geektutu.com/post/gee-day7.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/gee-web/day7-panic-recover)
## 推荐阅读

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB