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}}

+ + +