mirror of
https://github.com/geektutu/7days-golang.git
synced 2024-04-21 12:32:11 +00:00
add day6 template
This commit is contained in:
@@ -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")
|
||||
}
|
||||
```
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+19
-17
@@ -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
|
||||
// <h1>Hello Gee</h1>
|
||||
/*
|
||||
(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
|
||||
<h1>Hello Gee</h1>
|
||||
|
||||
// (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"),
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+22
-20
@@ -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
|
||||
// <h1>Hello Gee</h1>
|
||||
/*
|
||||
(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
|
||||
<h1>Hello Gee</h1>
|
||||
|
||||
// (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"),
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+22
-20
@@ -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
|
||||
// <h1>Hello Gee</h1>
|
||||
/*
|
||||
(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
|
||||
<h1>Hello Gee</h1>
|
||||
|
||||
// (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"),
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+19
-9
@@ -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/
|
||||
<h1>Hello Gee</h1>
|
||||
|
||||
// (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))
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
(1) render array
|
||||
$ curl http://localhost:9999/date
|
||||
<html>
|
||||
<body>
|
||||
<p>hello, gee</p>
|
||||
<p>Date: 2019-08-17</p>
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
|
||||
/*
|
||||
(2) custom render function
|
||||
$ curl http://localhost:9999/students
|
||||
<html>
|
||||
<body>
|
||||
<p>hello, gee</p>
|
||||
<p>0: Geektutu is 20 years old</p>
|
||||
<p>1: Jack is 22 years old</p>
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
|
||||
/*
|
||||
(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")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
I'm file1
|
||||
I'm file1
|
||||
I'm file1
|
||||
@@ -0,0 +1,5 @@
|
||||
p {
|
||||
color: orange;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- templates/arr.tmpl -->
|
||||
<html>
|
||||
<body>
|
||||
<p>hello, {{.title}}</p>
|
||||
{{range $index, $ele := .stuArr }}
|
||||
<p>{{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old</p>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
<html>
|
||||
<link rel="stylesheet" href="/assets/geektutu.css">
|
||||
<p>geektutu.css is loaded</p>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- templates/arr.tmpl -->
|
||||
<html>
|
||||
<body>
|
||||
<p>hello, {{.title}}</p>
|
||||
<p>Date: {{.now | formatAsDate}}</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user