add day5 middleware

This commit is contained in:
gzdaijie
2019-08-17 01:47:14 +08:00
parent 947c55dc95
commit c44f0625dc
16 changed files with 558 additions and 47 deletions
+38 -2
View File
@@ -1,14 +1,16 @@
# # 7天用Go从零实现Web框架Gee
# 7天用Go从零实现Web框架Gee
![Gee](doc/gee/gee.jpg)
Gee 的设计与实现参考了Gin,这个教程可以快速入门:[Go Gin简明教程](https://geektutu.com/post/quick-go-gin.html)。
## [教程目录](https://geektutu.com/post/gee.html)
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](day1-http-base)
- 第二天:Context上下文设计,[Code - Github](day2-context)
- 第三天:Tire树路由(Router)[Code - Github](day3-router)
- 第四天:分组控制(Group)[Code - Github](day4-group)
- 第五天:中间件(Middleware)
- 第五天:中间件(Middleware)[Code - Github](day5-middleware)
- 第六天:HTML模板(Template)
- 第七天:异常错误处理(Panic)
@@ -119,4 +121,38 @@ func main() {
r.Run(":9999")
}
```
## Day 5 - Middleware
```go
func onlyForV2() gee.HandlerFunc {
return func(c *gee.Context) {
// Start timer
t := time.Now()
// Process request
c.Next()
// Calculate resolution time
log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t))
}
}
func main() {
r := gee.New()
r.Use(gee.Logger()) // global midlleware
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
v2 := r.Group("/v2")
v2.Use(onlyForV2()) // v2 group middleware
{
v2.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
}
r.Run(":9999")
}
```
+12 -7
View File
@@ -7,18 +7,22 @@ import (
)
type Context struct {
Path string
Method string
// origin objects
Writer http.ResponseWriter
Req *http.Request
// request info
Path string
Method string
// response info
StatusCode int
}
func newContext(w http.ResponseWriter, req *http.Request) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
Req: req,
Writer: w,
}
}
@@ -31,6 +35,7 @@ func (c *Context) Query(key string) string {
}
func (c *Context) Status(code int) {
c.StatusCode = code
c.Writer.WriteHeader(code)
}
@@ -45,13 +50,13 @@ func (c *Context) String(code int, format string, values ...interface{}) {
}
func (c *Context) HTML(code int, html string) {
c.Writer.WriteHeader(code)
c.Status(code)
c.SetHeader("Content-Type", "text/html")
c.Writer.Write([]byte(html))
}
func (c *Context) JSON(code int, obj interface{}) {
c.Writer.WriteHeader(code)
c.Status(code)
c.SetHeader("Content-Type", "application/json")
encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(obj); err != nil {
@@ -60,6 +65,6 @@ func (c *Context) JSON(code int, obj interface{}) {
}
func (c *Context) Data(code int, data []byte) {
c.Writer.WriteHeader(code)
c.Status(code)
c.Writer.Write(data)
}
+13 -8
View File
@@ -7,20 +7,24 @@ import (
)
type Context struct {
Path string
Method string
// origin objects
Writer http.ResponseWriter
Req *http.Request
// request info
Path string
Method string
Params map[string]string
// response info
StatusCode int
}
func newContext(w http.ResponseWriter, req *http.Request, params map[string]string) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
Params: make(map[string]string),
Req: req,
Writer: w,
Params: params,
}
}
@@ -38,6 +42,7 @@ func (c *Context) Query(key string) string {
}
func (c *Context) Status(code int) {
c.StatusCode = code
c.Writer.WriteHeader(code)
}
@@ -52,13 +57,13 @@ func (c *Context) String(code int, format string, values ...interface{}) {
}
func (c *Context) HTML(code int, html string) {
c.Writer.WriteHeader(code)
c.Status(code)
c.SetHeader("Content-Type", "text/html")
c.Writer.Write([]byte(html))
}
func (c *Context) JSON(code int, obj interface{}) {
c.Writer.WriteHeader(code)
c.Status(code)
c.SetHeader("Content-Type", "application/json")
encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(obj); err != nil {
@@ -67,6 +72,6 @@ func (c *Context) JSON(code int, obj interface{}) {
}
func (c *Context) Data(code int, data []byte) {
c.Writer.WriteHeader(code)
c.Status(code)
c.Writer.Write(data)
}
+1 -1
View File
@@ -14,7 +14,7 @@ package main
// (3)
// $ curl "http://localhost:9999/hello/geektutu"
// hello , you're at /hello/geektutu
// hello geektutu, you're at /hello/geektutu
// (4)
// $ curl "http://localhost:9999/login" -X POST -d 'username=geektutu&password=1234'
+13 -8
View File
@@ -7,20 +7,24 @@ import (
)
type Context struct {
Path string
Method string
// origin objects
Writer http.ResponseWriter
Req *http.Request
// request info
Path string
Method string
Params map[string]string
// response info
StatusCode int
}
func newContext(w http.ResponseWriter, req *http.Request, params map[string]string) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
Params: make(map[string]string),
Req: req,
Writer: w,
Params: params,
}
}
@@ -38,6 +42,7 @@ func (c *Context) Query(key string) string {
}
func (c *Context) Status(code int) {
c.StatusCode = code
c.Writer.WriteHeader(code)
}
@@ -52,13 +57,13 @@ func (c *Context) String(code int, format string, values ...interface{}) {
}
func (c *Context) HTML(code int, html string) {
c.Writer.WriteHeader(code)
c.Status(code)
c.SetHeader("Content-Type", "text/html")
c.Writer.Write([]byte(html))
}
func (c *Context) JSON(code int, obj interface{}) {
c.Writer.WriteHeader(code)
c.Status(code)
c.SetHeader("Content-Type", "application/json")
encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(obj); err != nil {
@@ -67,6 +72,6 @@ func (c *Context) JSON(code int, obj interface{}) {
}
func (c *Context) Data(code int, data []byte) {
c.Writer.WriteHeader(code)
c.Status(code)
c.Writer.Write(data)
}
+11 -5
View File
@@ -10,14 +10,16 @@ type HandlerFunc func(*Context)
// Engine implement the interface of ServeHTTP
type (
RouterGroup struct {
prefix string
parent *RouterGroup // support nesting
engine *Engine // all groups share a Engine instance
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
}
)
@@ -25,17 +27,21 @@ type (
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 {
return &RouterGroup{
engine := group.engine
newGroup := &RouterGroup{
prefix: group.prefix + prefix,
parent: group,
engine: group.engine,
engine: engine,
}
engine.groups = append(engine.groups, newGroup)
return newGroup
}
func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {
+1 -1
View File
@@ -14,7 +14,7 @@ package main
// (3)
// $ curl "http://localhost:9999/v2/hello/geektutu"
// hello , you're at /v2/hello/geektutu
// hello geektutu, you're at /hello/geektutu
// (4)
// $ curl "http://localhost:9999/v2/login" -X POST -d 'username=geektutu&password=1234'
+92
View File
@@ -0,0 +1,92 @@
package gee
import (
"encoding/json"
"fmt"
"net/http"
)
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
}
func newContext(w http.ResponseWriter, req *http.Request,
params map[string]string, handlers []HandlerFunc) *Context {
return &Context{
Path: req.URL.Path,
Method: req.Method,
Params: params,
Req: req,
Writer: w,
handlers: handlers,
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) 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) 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")
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)
}
+82
View File
@@ -0,0 +1,82 @@
package gee
import (
"net/http"
"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
}
)
// 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)
}
// 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...)
}
}
engine.router.handle(w, req, middlewares)
}
+17
View File
@@ -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))
}
}
+89
View File
@@ -0,0 +1,89 @@
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(w http.ResponseWriter, req *http.Request, middlewares []HandlerFunc) {
n, params := r.getRoute(req.Method, req.URL.Path)
handlers := middlewares
if n != nil {
key := req.Method + "-" + n.pattern
handlers = append(middlewares, r.handlers[key])
} else {
handlers = append(middlewares, func(c *Context) {
c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
})
}
c := newContext(w, req, params, handlers)
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
}
+47
View File
@@ -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")
}
}
+80
View File
@@ -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
}
+48
View File
@@ -0,0 +1,48 @@
package main
// (1) global middleware Logger
// $ curl -i http://localhost:9999/
// 2019/08/17 01:37:38 [200] / in 3.14µs
// (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
import (
"log"
"net/http"
"time"
"./gee"
)
func onlyForV2() gee.HandlerFunc {
return func(c *gee.Context) {
// Start timer
t := time.Now()
// Process request
c.Next()
// Calculate resolution time
log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t))
}
}
func main() {
r := gee.New()
r.Use(gee.Logger()) // global midlleware
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
v2 := r.Group("/v2")
v2.Use(onlyForV2()) // v2 group middleware
{
v2.GET("/hello/:name", func(c *gee.Context) {
// expect /hello/geektutu
c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
}
r.Run(":9999")
}
+5 -5
View File
@@ -12,7 +12,7 @@ keywords:
- 动手写Web框架
- net/http
image: post/gee/gee.jpg
github: https://github.com/geektutu/7days-gee-golang-web
github: https://github.com/geektutu/7days-golang
---
本文是 [7天用Go从零实现Web框架Gee教程系列](https://geektutu.com/post/gee.html)的第一篇。
@@ -24,7 +24,7 @@ github: https://github.com/geektutu/7days-gee-golang-web
Go语言内置了 `net/http`库,封装了HTTP网络编程的基础的接口,我们实现的`Gee` Web 框架便是基于`net/http`的。我们接下来通过一个例子,简单介绍下这个库的使用。
**[day1-http-base/base1/main.go](https://github.com/geektutu/7days-gee-golang-web/tree/master/day1-http-base/base1)**
**[day1-http-base/base1/main.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base1)**
```go
package main
@@ -82,7 +82,7 @@ func ListenAndServe(address string, h Handler) error
第二个参数的类型是什么呢?通过查看`net/http`的源码可以发现,`Handler`是一个接口,需要实现方法 _ServeHTTP_ ,也就是说,只要传入任何实现了 _ServerHTTP_ 接口的实例,所有的HTTP请求,就都交给了该实例处理了。马上来试一试吧。
**[day1-http-base/base2/main.go](https://github.com/geektutu/7days-gee-golang-web/tree/master/day1-http-base/base2)**
**[day1-http-base/base2/main.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base2)**
```go
package main
@@ -135,7 +135,7 @@ main.go
### main.go
**[day1-http-base/base3/main.go](https://github.com/geektutu/7days-gee-golang-web/tree/master/day1-http-base/base3)**
**[day1-http-base/base3/main.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base3)**
```go
package main
@@ -167,7 +167,7 @@ func main() {
### gee.go
**[day1-http-base/base3/gee/gee.go](https://github.com/geektutu/7days-gee-golang-web/tree/master/day1-http-base/base3)**
**[day1-http-base/base3/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day1-http-base/base3)**
```go
package gee
+9 -10
View File
@@ -12,7 +12,7 @@ keywords:
- 动手写
- from scratch
image: post/gee/gee.jpg
github: https://github.com/geektutu/7days-gee-golang-web
github: https://github.com/geektutu/7days-golang
---
![gee](gee/gee.jpg)
@@ -58,15 +58,14 @@ func handler(w http.ResponseWriter, r *http.Request) {
时间关系,同时为了尽可能地简洁明了,这个框架中的很多部分实现的功能都很简单,但是尽可能地体现一个框架核心的设计原则。例如`Router`的设计,虽然支持的动态路由规则有限,但为了性能考虑匹配算法是用`Trie树`实现的,`Router`最重要的指标之一便是性能。
希望这个教程能够对你有所启发,如果对 Gee 有任何好的建议,欢迎提[issues - Github](https://github.com/geektutu/7days-gee-golang-web/issues) 和 PR。教程中的任何问题,可以直接在文章末尾评论。
希望这个教程能够对你有所启发,如果对 Gee 有任何好的建议,欢迎提[issues - Github](https://github.com/geektutu/7days-golang/issues) 和 PR。教程中的任何问题,可以直接在文章末尾评论。
## 目录
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](https://github.com/geektutu/7days-gee-golang-web/tree/master/day1-http-base)
- 第二天:Tire树实现路由(Router)
- 第三天:设计Context
- 第四天:支持模板(HTML Template)
- 第五天:支持中间件(Middleware)
- 第六天:简单鉴权(BASIC AUTH)
- 第七天:异常错误处理(Panic)
- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day1-http-base)
- 第二天:Context上下文设计,[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day2-context)
- 第三天:Tire树路由(Router)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day3-router)
- 第四天:分组控制(Group)[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day4-group)
- 第五天:中间件(Middleware)
- 第六天:HTML模板(Template)
- 第七天:异常错误处理(Panic)