diff --git a/README.md b/README.md
index c1d4d96..0b80e90 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Gee 的设计与实现参考了Gin,这个教程可以快速入门:[Go Gin简
- 第三天:Tire树路由(Router),[Code - Github](day3-router)
- 第四天:分组控制(Group),[Code - Github](day4-group)
- 第五天:中间件(Middleware),[Code - Github](day5-middleware)
-- 第六天:HTML模板(Template)
+- 第六天:HTML模板(Template),[Code - Github](day6-template)
- 第七天:异常错误处理(Panic)
@@ -155,4 +155,49 @@ func main() {
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")
+}
```
\ No newline at end of file
diff --git a/day2-context/gee/context.go b/day2-context/gee/context.go
index 41309f5..795bf16 100644
--- a/day2-context/gee/context.go
+++ b/day2-context/gee/context.go
@@ -6,6 +6,8 @@ import (
"net/http"
)
+type H map[string]interface{}
+
type Context struct {
// origin objects
Writer http.ResponseWriter
@@ -49,12 +51,6 @@ func (c *Context) String(code int, format string, values ...interface{}) {
c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
-func (c *Context) HTML(code int, html string) {
- c.Status(code)
- c.SetHeader("Content-Type", "text/html")
- c.Writer.Write([]byte(html))
-}
-
func (c *Context) JSON(code int, obj interface{}) {
c.Status(code)
c.SetHeader("Content-Type", "application/json")
@@ -68,3 +64,9 @@ func (c *Context) Data(code int, data []byte) {
c.Status(code)
c.Writer.Write(data)
}
+
+func (c *Context) HTML(code int, html string) {
+ c.Status(code)
+ c.SetHeader("Content-Type", "text/html")
+ c.Writer.Write([]byte(html))
+}
diff --git a/day2-context/gee/gee.go b/day2-context/gee/gee.go
index 822c1e2..ce0d4da 100644
--- a/day2-context/gee/gee.go
+++ b/day2-context/gee/gee.go
@@ -37,5 +37,6 @@ func (engine *Engine) Run(addr string) (err error) {
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- engine.router.handle(w, req)
+ c := newContext(w, req)
+ engine.router.handle(c)
}
diff --git a/day2-context/gee/router.go b/day2-context/gee/router.go
index 9221c29..6e76a10 100644
--- a/day2-context/gee/router.go
+++ b/day2-context/gee/router.go
@@ -17,9 +17,8 @@ func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
r.handlers[key] = handler
}
-func (r *router) handle(w http.ResponseWriter, req *http.Request) {
- key := req.Method + "-" + req.URL.Path
- c := newContext(w, req)
+func (r *router) handle(c *Context) {
+ key := c.Method + "-" + c.Path
if handler, ok := r.handlers[key]; ok {
handler(c)
} else {
diff --git a/day2-context/main.go b/day2-context/main.go
index 40b7158..a493d3f 100644
--- a/day2-context/main.go
+++ b/day2-context/main.go
@@ -1,24 +1,26 @@
package main
-// (1)
-// $ curl -i http://localhost:9999/
-// HTTP/1.1 200 OK
-// Date: Mon, 12 Aug 2019 16:52:52 GMT
-// Content-Length: 18
-// Content-Type: text/html; charset=utf-8
-//
Hello Gee
+/*
+(1)
+$ curl -i http://localhost:9999/
+HTTP/1.1 200 OK
+Date: Mon, 12 Aug 2019 16:52:52 GMT
+Content-Length: 18
+Content-Type: text/html; charset=utf-8
+Hello Gee
-// (2)
-// $ curl "http://localhost:9999/hello?name=geektutu"
-// hello geektutu, you're at /hello
+(2)
+$ curl "http://localhost:9999/hello?name=geektutu"
+hello geektutu, you're at /hello
-// (3)
-// $ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234'
-// {"password":"1234","username":"geektutu"}
+(3)
+$ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234'
+{"password":"1234","username":"geektutu"}
-// (4)
-// $ curl "http://localhost:9999/xxx"
-// 404 NOT FOUND: /xxx
+(4)
+$ curl "http://localhost:9999/xxx"
+404 NOT FOUND: /xxx
+*/
import (
"net/http"
@@ -37,7 +39,7 @@ func main() {
})
r.POST("/login", func(c *gee.Context) {
- c.JSON(http.StatusOK, &map[string]string{
+ c.JSON(http.StatusOK, gee.H{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
diff --git a/day3-router/gee/context.go b/day3-router/gee/context.go
index 08d6048..2733bdb 100644
--- a/day3-router/gee/context.go
+++ b/day3-router/gee/context.go
@@ -6,6 +6,8 @@ import (
"net/http"
)
+type H map[string]interface{}
+
type Context struct {
// origin objects
Writer http.ResponseWriter
@@ -18,13 +20,12 @@ type Context struct {
StatusCode int
}
-func newContext(w http.ResponseWriter, req *http.Request, params map[string]string) *Context {
+func newContext(w http.ResponseWriter, req *http.Request) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
- Params: params,
}
}
@@ -56,12 +57,6 @@ func (c *Context) String(code int, format string, values ...interface{}) {
c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
-func (c *Context) HTML(code int, html string) {
- c.Status(code)
- c.SetHeader("Content-Type", "text/html")
- c.Writer.Write([]byte(html))
-}
-
func (c *Context) JSON(code int, obj interface{}) {
c.Status(code)
c.SetHeader("Content-Type", "application/json")
@@ -75,3 +70,9 @@ func (c *Context) Data(code int, data []byte) {
c.Status(code)
c.Writer.Write(data)
}
+
+func (c *Context) HTML(code int, html string) {
+ c.Status(code)
+ c.SetHeader("Content-Type", "text/html")
+ c.Writer.Write([]byte(html))
+}
diff --git a/day3-router/gee/gee.go b/day3-router/gee/gee.go
index 822c1e2..ce0d4da 100644
--- a/day3-router/gee/gee.go
+++ b/day3-router/gee/gee.go
@@ -37,5 +37,6 @@ func (engine *Engine) Run(addr string) (err error) {
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- engine.router.handle(w, req)
+ c := newContext(w, req)
+ engine.router.handle(c)
}
diff --git a/day3-router/gee/router.go b/day3-router/gee/router.go
index 88938cd..4b873fb 100644
--- a/day3-router/gee/router.go
+++ b/day3-router/gee/router.go
@@ -28,10 +28,10 @@ func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
r.handlers[key] = handler
}
-func (r *router) handle(w http.ResponseWriter, req *http.Request) {
- n, params := r.getRoute(req.Method, req.URL.Path)
- c := newContext(w, req, params)
+func (r *router) handle(c *Context) {
+ n, params := r.getRoute(c.Method, c.Path)
if n != nil {
+ c.Params = params
key := c.Method + "-" + n.pattern
r.handlers[key](c)
} else {
diff --git a/day3-router/main.go b/day3-router/main.go
index 349c289..b636dfa 100644
--- a/day3-router/main.go
+++ b/day3-router/main.go
@@ -1,28 +1,30 @@
package main
-// (1)
-// $ curl -i http://localhost:9999/
-// HTTP/1.1 200 OK
-// Date: Mon, 12 Aug 2019 16:52:52 GMT
-// Content-Length: 18
-// Content-Type: text/html; charset=utf-8
-// Hello Gee
+/*
+(1)
+$ curl -i http://localhost:9999/
+HTTP/1.1 200 OK
+Date: Mon, 12 Aug 2019 16:52:52 GMT
+Content-Length: 18
+Content-Type: text/html; charset=utf-8
+Hello Gee
-// (2)
-// $ curl "http://localhost:9999/hello?name=geektutu"
-// hello geektutu, you're at /hello
+(2)
+$ curl "http://localhost:9999/hello?name=geektutu"
+hello geektutu, you're at /hello
-// (3)
-// $ curl "http://localhost:9999/hello/geektutu"
-// hello geektutu, you're at /hello/geektutu
+(3)
+$ curl "http://localhost:9999/hello/geektutu"
+hello geektutu, you're at /hello/geektutu
-// (4)
-// $ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234'
-// {"password":"1234","username":"geektutu"}
+(4)
+$ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234'
+{"password":"1234","username":"geektutu"}
-// (5)
-// $ curl "http://localhost:9999/xxx"
-// 404 NOT FOUND: /xxx
+(5)
+$ curl "http://localhost:9999/xxx"
+404 NOT FOUND: /xxx
+*/
import (
"net/http"
@@ -47,7 +49,7 @@ func main() {
})
r.POST("/login", func(c *gee.Context) {
- c.JSON(http.StatusOK, &map[string]string{
+ c.JSON(http.StatusOK, gee.H{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
diff --git a/day4-group/gee/context.go b/day4-group/gee/context.go
index 08d6048..2733bdb 100644
--- a/day4-group/gee/context.go
+++ b/day4-group/gee/context.go
@@ -6,6 +6,8 @@ import (
"net/http"
)
+type H map[string]interface{}
+
type Context struct {
// origin objects
Writer http.ResponseWriter
@@ -18,13 +20,12 @@ type Context struct {
StatusCode int
}
-func newContext(w http.ResponseWriter, req *http.Request, params map[string]string) *Context {
+func newContext(w http.ResponseWriter, req *http.Request) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
- Params: params,
}
}
@@ -56,12 +57,6 @@ func (c *Context) String(code int, format string, values ...interface{}) {
c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
-func (c *Context) HTML(code int, html string) {
- c.Status(code)
- c.SetHeader("Content-Type", "text/html")
- c.Writer.Write([]byte(html))
-}
-
func (c *Context) JSON(code int, obj interface{}) {
c.Status(code)
c.SetHeader("Content-Type", "application/json")
@@ -75,3 +70,9 @@ func (c *Context) Data(code int, data []byte) {
c.Status(code)
c.Writer.Write(data)
}
+
+func (c *Context) HTML(code int, html string) {
+ c.Status(code)
+ c.SetHeader("Content-Type", "text/html")
+ c.Writer.Write([]byte(html))
+}
diff --git a/day4-group/gee/gee.go b/day4-group/gee/gee.go
index 35c1299..b7ee570 100644
--- a/day4-group/gee/gee.go
+++ b/day4-group/gee/gee.go
@@ -65,5 +65,6 @@ func (engine *Engine) Run(addr string) (err error) {
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- engine.router.handle(w, req)
+ c := newContext(w, req)
+ engine.router.handle(c)
}
diff --git a/day4-group/gee/router.go b/day4-group/gee/router.go
index 88938cd..4b873fb 100644
--- a/day4-group/gee/router.go
+++ b/day4-group/gee/router.go
@@ -28,10 +28,10 @@ func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
r.handlers[key] = handler
}
-func (r *router) handle(w http.ResponseWriter, req *http.Request) {
- n, params := r.getRoute(req.Method, req.URL.Path)
- c := newContext(w, req, params)
+func (r *router) handle(c *Context) {
+ n, params := r.getRoute(c.Method, c.Path)
if n != nil {
+ c.Params = params
key := c.Method + "-" + n.pattern
r.handlers[key](c)
} else {
diff --git a/day4-group/main.go b/day4-group/main.go
index bab7c2e..1bd5346 100644
--- a/day4-group/main.go
+++ b/day4-group/main.go
@@ -1,28 +1,30 @@
package main
-// (1) v1
-// $ curl -i http://localhost:9999/v1/
-// HTTP/1.1 200 OK
-// Date: Mon, 12 Aug 2019 18:11:07 GMT
-// Content-Length: 18
-// Content-Type: text/html; charset=utf-8
-// Hello Gee
+/*
+(1) v1
+$ curl -i http://localhost:9999/v1/
+HTTP/1.1 200 OK
+Date: Mon, 12 Aug 2019 18:11:07 GMT
+Content-Length: 18
+Content-Type: text/html; charset=utf-8
+Hello Gee
-// (2)
-// $ curl "http://localhost:9999/v1/hello?name=geektutu"
-// hello geektutu, you're at /v1/hello
+(2)
+$ curl "http://localhost:9999/v1/hello?name=geektutu"
+hello geektutu, you're at /v1/hello
-// (3)
-// $ curl "http://localhost:9999/v2/hello/geektutu"
-// hello geektutu, you're at /hello/geektutu
+(3)
+$ curl "http://localhost:9999/v2/hello/geektutu"
+hello geektutu, you're at /hello/geektutu
-// (4)
-// $ curl "http://localhost:9999/v2/login" -X POST -d 'username=geektutu&password=1234'
-// {"password":"1234","username":"geektutu"}
+(4)
+$ curl "http://localhost:9999/v2/login" -X POST -d 'username=geektutu&password=1234'
+{"password":"1234","username":"geektutu"}
-// (5)
-// $ curl "http://localhost:9999/hello"
-// 404 NOT FOUND: /hello
+(5)
+$ curl "http://localhost:9999/hello"
+404 NOT FOUND: /hello
+*/
import (
"net/http"
@@ -50,7 +52,7 @@ func main() {
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{
+ c.JSON(http.StatusOK, gee.H{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
diff --git a/day5-middleware/gee/context.go b/day5-middleware/gee/context.go
index 8ba021e..63eca76 100644
--- a/day5-middleware/gee/context.go
+++ b/day5-middleware/gee/context.go
@@ -6,6 +6,8 @@ import (
"net/http"
)
+type H map[string]interface{}
+
type Context struct {
// origin objects
Writer http.ResponseWriter
@@ -21,17 +23,13 @@ type Context struct {
index int
}
-func newContext(w http.ResponseWriter, req *http.Request,
- params map[string]string, handlers []HandlerFunc) *Context {
-
+func newContext(w http.ResponseWriter, req *http.Request) *Context {
return &Context{
- Path: req.URL.Path,
- Method: req.Method,
- Params: params,
- Req: req,
- Writer: w,
- handlers: handlers,
- index: -1,
+ Path: req.URL.Path,
+ Method: req.Method,
+ Req: req,
+ Writer: w,
+ index: -1,
}
}
@@ -43,6 +41,11 @@ func (c *Context) Next() {
}
}
+func (c *Context) Fail(code int, err string) {
+ c.index = len(c.handlers)
+ c.JSON(code, H{"message": err})
+}
+
func (c *Context) Param(key string) string {
value, _ := c.Params[key]
return value
@@ -71,12 +74,6 @@ func (c *Context) String(code int, format string, values ...interface{}) {
c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
-func (c *Context) HTML(code int, html string) {
- c.Status(code)
- c.SetHeader("Content-Type", "text/html")
- c.Writer.Write([]byte(html))
-}
-
func (c *Context) JSON(code int, obj interface{}) {
c.Status(code)
c.SetHeader("Content-Type", "application/json")
@@ -90,3 +87,9 @@ func (c *Context) Data(code int, data []byte) {
c.Status(code)
c.Writer.Write(data)
}
+
+func (c *Context) HTML(code int, html string) {
+ c.Status(code)
+ c.SetHeader("Content-Type", "text/html")
+ c.Writer.Write([]byte(html))
+}
diff --git a/day5-middleware/gee/gee.go b/day5-middleware/gee/gee.go
index 3ba8446..29ae1d4 100644
--- a/day5-middleware/gee/gee.go
+++ b/day5-middleware/gee/gee.go
@@ -78,5 +78,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
middlewares = append(middlewares, group.middlewares...)
}
}
- engine.router.handle(w, req, middlewares)
+ c := newContext(w, req)
+ c.handlers = middlewares
+ engine.router.handle(c)
}
diff --git a/day5-middleware/gee/router.go b/day5-middleware/gee/router.go
index c87070f..4cc19dd 100644
--- a/day5-middleware/gee/router.go
+++ b/day5-middleware/gee/router.go
@@ -28,19 +28,18 @@ func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
r.handlers[key] = handler
}
-func (r *router) handle(w http.ResponseWriter, req *http.Request, middlewares []HandlerFunc) {
- n, params := r.getRoute(req.Method, req.URL.Path)
- handlers := middlewares
+func (r *router) handle(c *Context) {
+ n, params := r.getRoute(c.Method, c.Path)
if n != nil {
- key := req.Method + "-" + n.pattern
- handlers = append(middlewares, r.handlers[key])
+ key := c.Method + "-" + n.pattern
+ c.Params = params
+ c.handlers = append(c.handlers, r.handlers[key])
} else {
- handlers = append(middlewares, func(c *Context) {
+ c.handlers = append(c.handlers, func(c *Context) {
c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
})
}
- c := newContext(w, req, params, handlers)
c.Next()
}
diff --git a/day5-middleware/main.go b/day5-middleware/main.go
index 2d1d5e0..469de0d 100644
--- a/day5-middleware/main.go
+++ b/day5-middleware/main.go
@@ -1,13 +1,23 @@
package main
-// (1) global middleware Logger
-// $ curl -i http://localhost:9999/
-// 2019/08/17 01:37:38 [200] / in 3.14µs
+/*
+(1) global middleware Logger
+$ curl http://localhost:9999/
+Hello Gee
-// (2) global + group middleware
-// $ curl http://localhost:9999/v2/hello/geektutu
-// 2019/08/17 01:38:48 [200] /v2/hello/geektutu in 61.467µs for group v2
-// 2019/08/17 01:38:48 [200] /v2/hello/geektutu in 281µs
+>>> log
+2019/08/17 01:37:38 [200] / in 3.14µs
+*/
+
+/*
+(2) global + group middleware
+$ curl http://localhost:9999/v2/hello/geektutu
+{"message":"Internal Server Error"}
+
+>>> log
+2019/08/17 01:38:48 [200] /v2/hello/geektutu in 61.467µs for group v2
+2019/08/17 01:38:48 [200] /v2/hello/geektutu in 281µs
+*/
import (
"log"
@@ -21,8 +31,8 @@ func onlyForV2() gee.HandlerFunc {
return func(c *gee.Context) {
// Start timer
t := time.Now()
- // Process request
- c.Next()
+ // 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))
}
diff --git a/day6-template/gee/context.go b/day6-template/gee/context.go
new file mode 100644
index 0000000..4e16ca2
--- /dev/null
+++ b/day6-template/gee/context.go
@@ -0,0 +1,101 @@
+package gee
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+)
+
+type H map[string]interface{}
+
+type Context struct {
+ // origin objects
+ Writer http.ResponseWriter
+ Req *http.Request
+ // request info
+ Path string
+ Method string
+ Params map[string]string
+ // response info
+ StatusCode int
+ // middleware
+ handlers []HandlerFunc
+ index int
+ // engine pointer
+ engine *Engine
+}
+
+func newContext(w http.ResponseWriter, req *http.Request) *Context {
+ return &Context{
+ Path: req.URL.Path,
+ Method: req.Method,
+ Req: req,
+ Writer: w,
+ index: -1,
+ }
+}
+
+func (c *Context) Next() {
+ c.index++
+ s := len(c.handlers)
+ for ; c.index < s; c.index++ {
+ c.handlers[c.index](c)
+ }
+}
+
+func (c *Context) Fail(code int, err string) {
+ c.index = len(c.handlers)
+ c.JSON(code, H{"message": err})
+}
+
+func (c *Context) Param(key string) string {
+ value, _ := c.Params[key]
+ return value
+}
+
+func (c *Context) PostForm(key string) string {
+ return c.Req.FormValue(key)
+}
+
+func (c *Context) Query(key string) string {
+ return c.Req.URL.Query().Get(key)
+}
+
+func (c *Context) Status(code int) {
+ c.StatusCode = code
+ c.Writer.WriteHeader(code)
+}
+
+func (c *Context) SetHeader(key string, value string) {
+ c.Writer.Header().Set(key, value)
+}
+
+func (c *Context) String(code int, format string, values ...interface{}) {
+ c.Status(code)
+ c.SetHeader("Content-Type", "text/plain")
+ c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
+}
+
+func (c *Context) JSON(code int, obj interface{}) {
+ c.Status(code)
+ c.SetHeader("Content-Type", "application/json")
+ encoder := json.NewEncoder(c.Writer)
+ if err := encoder.Encode(obj); err != nil {
+ http.Error(c.Writer, err.Error(), 500)
+ }
+}
+
+func (c *Context) Data(code int, data []byte) {
+ c.Status(code)
+ c.Writer.Write(data)
+}
+
+// HTML template render
+// refer https://golang.org/pkg/html/template/
+func (c *Context) HTML(code int, name string, data interface{}) {
+ c.Writer.WriteHeader(code)
+ c.Writer.Header().Set("Content-Type", "text/html")
+ if err := c.engine.htmlTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
+ c.Fail(500, err.Error())
+ }
+}
diff --git a/day6-template/gee/gee.go b/day6-template/gee/gee.go
new file mode 100644
index 0000000..d1f2f6e
--- /dev/null
+++ b/day6-template/gee/gee.go
@@ -0,0 +1,122 @@
+package gee
+
+import (
+ "html/template"
+ "net/http"
+ "path"
+ "strings"
+)
+
+// HandlerFunc defines the request handler used by gee
+type HandlerFunc func(*Context)
+
+// Engine implement the interface of ServeHTTP
+type (
+ RouterGroup struct {
+ prefix string
+ middlewares []HandlerFunc // support middleware
+ parent *RouterGroup // support nesting
+ engine *Engine // all groups share a Engine instance
+ }
+
+ Engine struct {
+ *RouterGroup
+ router *router
+ groups []*RouterGroup // store all group
+ htmlTemplates *template.Template // for html render
+ funcMap template.FuncMap
+ }
+)
+
+// New is the constructor of gee.Engine
+func New() *Engine {
+ engine := &Engine{router: newRouter()}
+ engine.RouterGroup = &RouterGroup{engine: engine}
+ engine.groups = []*RouterGroup{engine.RouterGroup}
+ return engine
+}
+
+// Group is defined to create a new RouterGroup
+// remember all groups share the same Engine instance
+func (group *RouterGroup) Group(prefix string) *RouterGroup {
+ engine := group.engine
+ newGroup := &RouterGroup{
+ prefix: group.prefix + prefix,
+ parent: group,
+ engine: engine,
+ }
+ engine.groups = append(engine.groups, newGroup)
+ return newGroup
+}
+
+// Use is defined to add middleware to the group
+func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
+ group.middlewares = append(group.middlewares, middlewares...)
+}
+
+func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {
+ pattern := group.prefix + comp
+
+ group.engine.router.addRoute(method, pattern, handler)
+}
+
+// GET defines the method to add GET request
+func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
+ group.addRoute("GET", pattern, handler)
+}
+
+// POST defines the method to add POST request
+func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
+ group.addRoute("POST", pattern, handler)
+}
+
+// create static handler
+func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
+ absolutePath := path.Join(group.prefix, relativePath)
+ fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
+ return func(c *Context) {
+ file := c.Param("/filepath")
+ // Check if file exists and/or if we have permission to access it
+ if _, err := fs.Open(file); err != nil {
+ c.Status(http.StatusNotFound)
+ return
+ }
+
+ fileServer.ServeHTTP(c.Writer, c.Req)
+ }
+}
+
+// serve static files
+func (group *RouterGroup) Static(relativePath string, root string) {
+ handler := group.createStaticHandler(relativePath, http.Dir(root))
+ urlPattern := path.Join(relativePath, "/:filepath")
+ // Register GET handlers
+ group.GET(urlPattern, handler)
+}
+
+// for custom render function
+func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
+ engine.funcMap = funcMap
+}
+
+func (engine *Engine) LoadHTMLGlob(pattern string) {
+ engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern))
+}
+
+// Run defines the method to start a http server
+func (engine *Engine) Run(addr string) (err error) {
+ return http.ListenAndServe(addr, engine)
+}
+
+func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ var middlewares []HandlerFunc
+ for _, group := range engine.groups {
+ if strings.HasPrefix(req.URL.Path, group.prefix) {
+ middlewares = append(middlewares, group.middlewares...)
+ }
+ }
+ c := newContext(w, req)
+ c.handlers = middlewares
+ c.engine = engine
+ engine.router.handle(c)
+}
diff --git a/day6-template/gee/logger.go b/day6-template/gee/logger.go
new file mode 100644
index 0000000..a4df935
--- /dev/null
+++ b/day6-template/gee/logger.go
@@ -0,0 +1,17 @@
+package gee
+
+import (
+ "log"
+ "time"
+)
+
+func Logger() HandlerFunc {
+ return func(c *Context) {
+ // Start timer
+ t := time.Now()
+ // Process request
+ c.Next()
+ // Calculate resolution time
+ log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))
+ }
+}
diff --git a/day6-template/gee/router.go b/day6-template/gee/router.go
new file mode 100644
index 0000000..4cc19dd
--- /dev/null
+++ b/day6-template/gee/router.go
@@ -0,0 +1,88 @@
+package gee
+
+import (
+ "net/http"
+ "strings"
+)
+
+type router struct {
+ roots map[string]*node
+ handlers map[string]HandlerFunc
+}
+
+func newRouter() *router {
+ return &router{
+ roots: make(map[string]*node),
+ handlers: make(map[string]HandlerFunc),
+ }
+}
+
+func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
+ parts := filterNonEmpty(strings.Split(pattern, "/"))
+ key := method + "-" + pattern
+ _, ok := r.roots[method]
+ if !ok {
+ r.roots[method] = &node{}
+ }
+ r.roots[method].insert(pattern, parts, 0)
+ r.handlers[key] = handler
+}
+
+func (r *router) handle(c *Context) {
+ n, params := r.getRoute(c.Method, c.Path)
+
+ if n != nil {
+ key := c.Method + "-" + n.pattern
+ c.Params = params
+ c.handlers = append(c.handlers, r.handlers[key])
+ } else {
+ c.handlers = append(c.handlers, func(c *Context) {
+ c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
+ })
+ }
+ c.Next()
+}
+
+func (r *router) getRoute(method string, pattern string) (*node, map[string]string) {
+ searchParts := filterNonEmpty(strings.Split(pattern, "/"))
+ params := make(map[string]string)
+ root, ok := r.roots[method]
+
+ if !ok {
+ return nil, nil
+ }
+
+ n := root.search(searchParts, 0)
+
+ if n != nil {
+ parts := filterNonEmpty(strings.Split(n.pattern, "/"))
+ for index, part := range parts {
+ if part[0] == ':' {
+ params[part[1:]] = searchParts[index]
+ }
+ }
+ return n, params
+ }
+
+ return nil, nil
+}
+
+func (r *router) getRoutes(method string) []*node {
+ root, ok := r.roots[method]
+ if !ok {
+ return nil
+ }
+ nodes := make([]*node, 0)
+ root.travel(&nodes)
+ return nodes
+}
+
+func filterNonEmpty(vs []string) []string {
+ parts := make([]string, 0)
+ for _, item := range vs {
+ if item != "" {
+ parts = append(parts, item)
+ }
+ }
+ return parts
+}
diff --git a/day6-template/gee/router_test.go b/day6-template/gee/router_test.go
new file mode 100644
index 0000000..76c81b2
--- /dev/null
+++ b/day6-template/gee/router_test.go
@@ -0,0 +1,47 @@
+package gee
+
+import (
+ "fmt"
+ "testing"
+)
+
+func newTestRouter() *router {
+ r := newRouter()
+ r.addRoute("GET", "/", nil)
+ r.addRoute("GET", "/hello/:name", nil)
+ r.addRoute("GET", "/hello/b/c", nil)
+ r.addRoute("GET", "/hi/:name", nil)
+ return r
+}
+
+func TestGetRoute(t *testing.T) {
+ r := newTestRouter()
+ n, ps := r.getRoute("GET", "/hello/geektutu")
+
+ if n == nil {
+ t.Fatal("nil shouldn't be returned")
+ }
+
+ if n.pattern != "/hello/:name" {
+ t.Fatal("should match /hello/:name")
+ }
+
+ if ps["name"] != "geektutu" {
+ t.Fatal("name should be equal to 'geektutu'")
+ }
+
+ fmt.Printf("matched path: %s, params['name']: %s\n", n.pattern, ps["name"])
+
+}
+
+func TestGetRoutes(t *testing.T) {
+ r := newTestRouter()
+ nodes := r.getRoutes("GET")
+ for i, n := range nodes {
+ fmt.Println(i+1, n)
+ }
+
+ if len(nodes) != 4 {
+ t.Fatal("the number of routes shoule be 4")
+ }
+}
diff --git a/day6-template/gee/trie.go b/day6-template/gee/trie.go
new file mode 100644
index 0000000..86697f0
--- /dev/null
+++ b/day6-template/gee/trie.go
@@ -0,0 +1,80 @@
+package gee
+
+import (
+ "fmt"
+)
+
+type node struct {
+ pattern string
+ part string
+ children []*node
+ isWild bool
+}
+
+func (n *node) String() string {
+ return fmt.Sprintf("node{pattern=%s, part=%s, isWild=%t}", n.pattern, n.part, n.isWild)
+}
+
+func (n *node) insert(pattern string, parts []string, height int) {
+ if len(parts) == height {
+ n.pattern = pattern
+ return
+ }
+
+ part := parts[height]
+ child := n.matchChild(part)
+ if child == nil {
+ child = &node{part: part, isWild: part[0] == ':'}
+ n.children = append(n.children, child)
+ }
+ child.insert(pattern, parts, height+1)
+}
+
+func (n *node) search(parts []string, height int) *node {
+ if len(parts) == height {
+ if n.pattern == "" {
+ return nil
+ }
+ return n
+ }
+
+ part := parts[height]
+ children := n.matchChildren(part)
+
+ for _, child := range children {
+ result := child.search(parts, height+1)
+ if result != nil {
+ return result
+ }
+ }
+
+ return nil
+}
+
+func (n *node) travel(list *([]*node)) {
+ if n.pattern != "" {
+ *list = append(*list, n)
+ }
+ for _, child := range n.children {
+ child.travel(list)
+ }
+}
+
+func (n *node) matchChild(part string) *node {
+ for _, child := range n.children {
+ if child.part == part || child.isWild {
+ return child
+ }
+ }
+ return nil
+}
+
+func (n *node) matchChildren(part string) []*node {
+ nodes := make([]*node, 0)
+ for _, child := range n.children {
+ if child.part == part || child.isWild {
+ nodes = append(nodes, child)
+ }
+ }
+ return nodes
+}
diff --git a/day6-template/main.go b/day6-template/main.go
new file mode 100644
index 0000000..92d4fe5
--- /dev/null
+++ b/day6-template/main.go
@@ -0,0 +1,84 @@
+package main
+
+/*
+(1) render array
+$ curl http://localhost:9999/date
+
+
+ hello, gee
+ Date: 2019-08-17
+
+
+*/
+
+/*
+(2) custom render function
+$ curl http://localhost:9999/students
+
+
+ hello, gee
+ 0: Geektutu is 20 years old
+ 1: Jack is 22 years old
+
+
+*/
+
+/*
+(3) serve static files
+$ curl http://localhost:9999/assets/geektutu.css
+p {
+ color: orange;
+ font-weight: 700;
+ font-size: 20px;
+}
+*/
+
+import (
+ "fmt"
+ "html/template"
+ "net/http"
+ "time"
+
+ "./gee"
+)
+
+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")
+}
diff --git a/day6-template/static/file1.txt b/day6-template/static/file1.txt
new file mode 100644
index 0000000..487492a
--- /dev/null
+++ b/day6-template/static/file1.txt
@@ -0,0 +1,3 @@
+I'm file1
+I'm file1
+I'm file1
diff --git a/day6-template/static/geektutu.css b/day6-template/static/geektutu.css
new file mode 100644
index 0000000..140157f
--- /dev/null
+++ b/day6-template/static/geektutu.css
@@ -0,0 +1,5 @@
+p {
+ color: orange;
+ font-weight: 700;
+ font-size: 20px;
+}
diff --git a/day6-template/templates/arr.tmpl b/day6-template/templates/arr.tmpl
new file mode 100644
index 0000000..9c8d9eb
--- /dev/null
+++ b/day6-template/templates/arr.tmpl
@@ -0,0 +1,9 @@
+
+
+
+ hello, {{.title}}
+ {{range $index, $ele := .stuArr }}
+ {{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old
+ {{ end }}
+
+
\ No newline at end of file
diff --git a/day6-template/templates/css.tmpl b/day6-template/templates/css.tmpl
new file mode 100644
index 0000000..3d06c20
--- /dev/null
+++ b/day6-template/templates/css.tmpl
@@ -0,0 +1,4 @@
+
+
+ geektutu.css is loaded
+
\ No newline at end of file
diff --git a/day6-template/templates/custom_func.tmpl b/day6-template/templates/custom_func.tmpl
new file mode 100644
index 0000000..2d50a8e
--- /dev/null
+++ b/day6-template/templates/custom_func.tmpl
@@ -0,0 +1,8 @@
+
+
+
+ hello, {{.title}}
+ Date: {{.now | formatAsDate}}
+
+
+