echo源码分析
共 29318字,需浏览 59分钟
·
2021-05-10 17:59
web框架的核心作用有三个:分层、路由、中间件。针对于go比较有名的web框架有
https://github.com/labstack/echo
https://github.com/gin-gonic/gin
https://github.com/kataras/iris
https://beego.me/docs/intro/
https://github.com/go-martini/martini
其中echo 是一个比较轻量级的框架,下面基于echo@v1.4.4对它的源码进行分析。
主要有下面6个文件和三个目录组成。
binder.go
context.go
echo.go
group.go
response.go
router.go
middleware
_fixture
website
其中middleware里面定义了最基本最常用的四个中间件
auth.go
compress.go
logger.go
recover.go
_fixture是一些网页资源
% ls _fixture
favicon.ico folder images index.html
website 是说明文档,中间有个Dockerfile 可以在本地编译镜像,跑起来
% ls website
Dockerfile config.json layouts
argo.json content static
首先我们看下如何使用echo
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
// 创建一个echo实例
e := echo.New()
// 注册中间件
// 需要我们在入口文件手动注入基础中间件
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// 注册路由
e.GET("/", hello)
// 启动服务
e.Logger.Fatal(e.Start(":1323"))
}
// 路由handle提出来了而已
// 匿名函数方式 不重要
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
1,echo.go文件
New函数定义在echo.go 文件里面
func New() (e *Echo) {
e = &Echo{
// 创建一个http Server指针
Server: new(http.Server),
// 创建一个https的 Server指针
TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
// 日志实例
Logger: log.New("echo"),
// 控制台、日志可以彩色输出的实例
colorer: color.New(),
maxParam: new(int),
}
// http server绑定实现了server.Handler的实例
// 也就是说Echo框架自身实现了http.Handler接口
e.Server.Handler = e
// https server绑定实现了server.Handler的实例
e.TLSServer.Handler = e
// 绑定http服务异常处理的handler
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
//
e.Binder = &DefaultBinder{}
// 设置日志输出级别
e.Logger.SetLevel(log.ERROR)
// 绑定标准日志输出实例
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
// 绑定获取请求上下文实例的闭包
e.pool.New = func() interface{} {
return e.NewContext(nil, nil)
}
// 绑定路由实例
e.router = NewRouter(e)
// 绑定路由map
// 注意这个属性的含义:路由分组用的,key为host,则按host分组
// 记住与Router.routes区别
// Router.routes存的路由的信息(不包含路由的handler)
e.routers = map[string]*Router{}
return
}
手先初始化了一个echo对象,然后定义了一个DefaultBinder,实现了Binder接口,这个接口定义在bind.go文件里,后面介绍
Binder interface {
Bind(i interface{}, c Context) error
}
接着设置了日志级别为ERROR,设置了标准输出Logger,然后初始化了一个Context 对象
// NewContext returns a Context instance.
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
return &context{
request: r,
response: NewResponse(w, e),
store: make(Map),
echo: e,
pvalues: make([]string, *e.maxParam),
handler: NotFoundHandler,
}
}
context 对象和interface 都定义在context.go文件中,后面讲解context .go文件的时候详细讲解.
其中的Map定义如下
Map map[string]interface{}
context里面存储了
r *http.Request, w http.ResponseWriter
这也就是为什么写echo的controller的时候只用传 context对象就行,而标准的http包需要顶替包含参数r *http.Request, w http.ResponseWriter的HandleFunc
最后初始化了路由(router.go)里面实现的
func NewRouter(e *Echo) *Router {
// 初始化Router
return &Router{
// 路由树
// 路由的信息(包含路由的handler)
// 查找路由用的LCP (最长公共前缀)算法
tree: &node{
// 节点对应的不同http method的handler
methodHandler: new(methodHandler),
},
// Router.routes存的路由的信息(不包含路由的handler)
routes: map[string]*Route{},
// 框架实例自身
echo: e,
}
Router的定义如下
Router struct {
tree *node
routes map[string]*Route
echo *Echo
}
是一个棵多叉树,其中tree存储了当前节点里面的值,它的定义如下
node struct {
kind kind
label byte
prefix string
parent *node
children children
ppath string
pnames []string
methodHandler *methodHandler
}
接着我们看下如何定义路由的,已get请求为例
e.GET("/stats", func(c echo.Context) error {
return c.JSON(200, s.Data())
})
它的定义如下
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodGet, path, h, m...)
}
我们可以看到,它的内部调用了Add方法,其实POST、PATCH、DELETE等http method类似都是对Add方法进行了包裹
Add方法的参数有http请求的方法,请求路径,对应的处理函数和中间件参数。
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
// 获取handler的名称
// 😨这个方法里面尽然用了反射获取name 只是个name有必要么 没别的办法了吗?
name := handlerName(handler)
// 注册路由
// 注意第三个参数是个闭包 匹配到路由就会执行这个闭包
e.router.Add(method, path, func(c Context) error {
h := handler
// Chain middleware
for i := len(middleware) - 1; i >= 0; i-- {
// 注意这里的中间件是这个路由专属的
// 而Use、Pre注册的中间件是全局公共的
// 遍历中间件
// 注意返回值类型是HandlerFunc
//典型的洋葱模式
h = middleware[i](h)
}
// 执行最后一个中间件
return h(c)
})
// 本次注册进来的路由的信息,只存放了handler的方法名
r := &Route{
Method: method,
Path: path,
Name: name,
}
// map存路由信息
e.router.routes[method+path] = r
return r
}
注意这里的Route 不是Router定义如下
Route struct {
Method string `json:"method"`
Path string `json:"path"`
Name string `json:"name"`
}
Method 存的是通过反射获取handler的名字
func handlerName(h HandlerFunc) string {
t := reflect.ValueOf(h).Type()
if t.Kind() == reflect.Func {
return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
}
return t.String()
}
最后看下start函数
e.Logger.Fatal(e.Start(":1323"))
func (e *Echo) Start(address string) error {
e.Server.Addr = address
return e.StartServer(e.Server)
}
func (e *Echo) StartServer(s *http.Server) (err error) {
// Setup
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
// 设置框架实例到http server的Handler
// Echo框架结构体实现了http.Handler接口
s.Handler = e
if e.Debug {
e.Logger.SetLevel(log.DEBUG)
}
if !e.HideBanner {
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
}
if s.TLSConfig == nil {
if e.Listener == nil {
// 监听ip+port
e.Listener, err = newListener(s.Addr)
if err != nil {
return err
}
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
// 启动http server
return s.Serve(e.Listener)
}
if e.TLSListener == nil {
// 设置https配置
l, err := newListener(s.Addr)
if err != nil {
return err
}
e.TLSListener = tls.NewListener(l, s.TLSConfig)
}
if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
return s.Serve(e.TLSListener)
}
接下来的流程就是标准httpserver的执行流程
s.Serve()
⬇️
// accept网络请求
rw, e := l.Accept()
⬇️
// goroutine处理请求
go c.serve(ctx)
⬇️
// 执行serverHandler的ServeHTTP
serverHandler{c.server}.ServeHTTP(w, w.req)
⬇️
// 执行当前框架实例的ServeHTTP方法
handler.ServeHTTP(rw, req)
看下echo是怎么实现ServeHTTP方法的
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
// 获取上下文实例
c := e.pool.Get().(*context)
c.Reset(r, w)
h := NotFoundHandler
// 不存在预执行中间件时
// 说说这个预执行中间件的含义:
// 看源码注释的含义是在寻找到路由之前执行的中间件
// 简单来说和普通中间件的的区别就是,还没走到匹配路由的逻辑就会执行的中间件,从下面来看只是代码逻辑的区别,实际的中间件执行顺序还是谁先注册谁先执行。所以无论是存在普通中间件还是预执行中间件,路由的handle总是最后执行。
// 个人感觉预执行中间件的意义不大
if e.premiddleware == nil {
// 先找当前host组的router
// LCP算法寻找当前path的handler
e.router.Find(r.Method, getPath(r), c)
h = c.Handler()
for i := len(e.middleware) - 1; i >= 0; i-- {
h = e.middleware[i](h)
}
} else {
// 看见这个预执行中间件的区别了吧
// 把注册普通中间件的逻辑又包装成了一个HandlerFunc注册到中间件链中
h = func(c Context) error {
e.router.Find(r.Method, getPath(r), c)
h := c.Handler()
for i := len(e.middleware) - 1; i >= 0; i-- {
h = e.middleware[i](h)
}
return h(c)
}
for i := len(e.premiddleware) - 1; i >= 0; i-- {
h = e.premiddleware[i](h)
}
}
// Execute chain
// 执行中间件链
// 在applyMiddleware中所有中间件构成了一个链
if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}
// Release context
// 释放上下文
e.pool.Put(c)
}
echo.go的核心逻辑基本讲完了,里面还定义了一系列的辅助类型和方法
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context)
// Validator is the interface that wraps the Validate function.
Validator interface {
Validate(i interface{}) error
}
// Renderer is the interface that wraps the Render function.
Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}
// i is the interface for Echo and Group.
i interface {
GET(string, HandlerFunc, ...MiddlewareFunc) *Route
}
// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
const (
CONNECT = http.MethodConnect
DELETE = http.MethodDelete
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
// Headers
const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
// Errors
var (
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
// with status code.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) *Route {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
return static(e, prefix, root)
}
func static(i i, prefix, root string) *Route {
h := func(c Context) error {
p, err := url.PathUnescape(c.Param("*"))
if err != nil {
return err
}
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
return c.File(name)
}
i.GET(prefix, h)
if prefix == "/" {
return i.GET(prefix+"*", h)
}
return i.GET(prefix+"/*", h)
}
把url中的namd/:id 中的id解析出来存到url中
// Reverse generates an URL from route name and provided parameters.
func (e *Echo) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer)
ln := len(params)
n := 0
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if r.Path[i] == ':' && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(r.Path[i])
}
}
break
}
}
return uri.String()
}
2,context.go文件
首先定义了
Context interface {}
和对应的对象
context struct {
request *http.Request
response *Response
path string
pnames []string
pvalues []string
query url.Values
handler HandlerFunc
store Map
echo *Echo
}
获取参数值
func (c *context) Param(name string) string {
for i, n := range c.pnames {
if i < len(c.pvalues) {
if n == name {
return c.pvalues[i]
}
}
}
return ""
}
func (c *context) QueryParam(name string) string {
if c.query == nil {
c.query = c.request.URL.Query()
}
return c.query.Get(name)
}
func (c *context) FormValue(name string) string {
return c.request.FormValue(name)
}
参数绑定
func (c *context) Bind(i interface{}) error {
return c.echo.Binder.Bind(i, c)
}
参数校验
func (c *context) Validate(i interface{}) error {
if c.echo.Validator == nil {
return ErrValidatorNotRegistered
}
return c.echo.Validator.Validate(i)
}
输出json
func (c *context) json(code int, i interface{}, indent string) error {
enc := json.NewEncoder(c.response)
if indent != "" {
enc.SetIndent("", indent)
}
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
c.response.WriteHeader(code)
return enc.Encode(i)
}
3,router.go
主要是定义了Router的一系列结构体
Router struct {
tree *node
routes []Route
echo *Echo
}
node struct {
kind kind
label byte
prefix string
parent *node
children children
ppath string
pnames []string
methodHandler *methodHandler
echo *Echo
}
一系列处理方法
methodHandler struct {
connect HandlerFunc
delete HandlerFunc
get HandlerFunc
head HandlerFunc
options HandlerFunc
patch HandlerFunc
post HandlerFunc
put HandlerFunc
trace HandlerFunc
}
初始化一个router对象
func NewRouter(e *Echo) *Router {
return &Router{
tree: &node{
methodHandler: new(methodHandler),
},
routes: []Route{},
echo: e,
}
}
添加路由,构建tire树
// Add registers a new route with a matcher for the URL path.
func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) {
ppath := path // Pristine path
pnames := []string{} // Param names
for i, l := 0, len(path); i < l; i++ {
if path[i] == ':' {
j := i + 1
//提取路由中的参数前面部分,构建树
r.insert(method, path[:i], nil, skind, "", nil, e)
for ; i < l && path[i] != '/'; i++ {
}
//把参数名放到pnames里面
pnames = append(pnames, path[j:i])
path = path[:j] + path[i:]
i, l = j, len(path)
if i == l {
r.insert(method, path[:i], h, pkind, ppath, pnames, e)
return
}
r.insert(method, path[:i], nil, pkind, ppath, pnames, e)
} else if path[i] == '*' {
r.insert(method, path[:i], nil, skind, "", nil, e)
pnames = append(pnames, "_*")
r.insert(method, path[:i+1], h, akind, ppath, pnames, e)
return
}
}
r.insert(method, path, h, skind, ppath, pnames, e)
}
提取参数和正则以后,构建tire树
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string, e *Echo) {
// Adjust max param
l := len(pnames)
if *e.maxParam < l {
*e.maxParam = l
}
cn := r.tree // Current node as root
if cn == nil {
panic("echo => invalid method")
}
search := path
for {
sl := len(search)
pl := len(cn.prefix)
l := 0
// LCP
max := pl
if sl < max {
max = sl
}
for ; l < max && search[l] == cn.prefix[l]; l++ {
}
if l == 0 {
// At root node
cn.label = search[0]
cn.prefix = search
if h != nil {
cn.kind = t
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
cn.echo = e
}
} else if l < pl {
// Split node
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames, cn.echo)
// Reset parent node
cn.kind = skind
cn.label = cn.prefix[0]
cn.prefix = cn.prefix[:l]
cn.children = nil
cn.methodHandler = new(methodHandler)
cn.ppath = ""
cn.pnames = nil
cn.echo = nil
cn.addChild(n)
if l == sl {
// At parent node
cn.kind = t
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
cn.echo = e
} else {
// Create child node
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames, e)
n.addHandler(method, h)
cn.addChild(n)
}
} else if l < sl {
search = search[l:]
c := cn.findChildWithLabel(search[0])
if c != nil {
// Go deeper
cn = c
continue
}
// Create child node
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames, e)
n.addHandler(method, h)
cn.addChild(n)
} else {
// Node already exists
if h != nil {
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
cn.echo = e
}
}
return
}
}
接着就是路由查找的过程
func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) {
// r.tree.printTree("", true)
h = notFoundHandler
e = r.echo
cn := r.tree // Current node as root
var (
search = path
c *node // Child node
n int // Param counter
nk kind // Next kind
nn *node // Next node
ns string // Next search
)
// Search order static > param > any
for {
if search == "" {
goto End
}
pl := 0 // Prefix length
l := 0 // LCP length
if cn.label != ':' {
sl := len(search)
pl = len(cn.prefix)
// LCP
max := pl
if sl < max {
max = sl
}
for ; l < max && search[l] == cn.prefix[l]; l++ {
}
}
if l == pl {
// Continue search
search = search[l:]
} else {
cn = nn
search = ns
if nk == pkind {
goto Param
} else if nk == akind {
goto Any
}
// Not found
return
}
if search == "" {
goto End
}
// Static node
if c = cn.findChild(search[0], skind); c != nil {
// Save next
if cn.prefix[len(cn.prefix)-1] == '/' {
nk = pkind
nn = cn
ns = search
}
cn = c
continue
}
// Param node
Param:
if c = cn.findChildByKind(pkind); c != nil {
// Issue #378
if len(ctx.pvalues) == n {
continue
}
// Save next
if cn.prefix[len(cn.prefix)-1] == '/' {
nk = akind
nn = cn
ns = search
}
cn = c
i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ {
}
ctx.pvalues[n] = search[:i]
n++
search = search[i:]
continue
}
// Any node
Any:
if cn = cn.findChildByKind(akind); cn == nil {
if nn != nil {
cn = nn
nn = nil // Next
search = ns
if nk == pkind {
goto Param
} else if nk == akind {
goto Any
}
}
// Not found
return
}
ctx.pvalues[len(cn.pnames)-1] = search
goto End
}
End:
ctx.path = cn.ppath
ctx.pnames = cn.pnames
h = cn.findHandler(method)
if cn.echo != nil {
e = cn.echo
}
// NOTE: Slow zone...
if h == nil {
h = cn.check405()
// Dig further for any, might have an empty value for *, e.g.
// serving a directory. Issue #207.
if cn = cn.findChildByKind(akind); cn == nil {
return
}
ctx.pvalues[len(cn.pnames)-1] = ""
if h = cn.findHandler(method); h == nil {
h = cn.check405()
}
}
return
}
最后是serveHTTP方法,这个方法是在echo的同名方法里,把router转化成handleFunc,然后调用的。
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := r.echo.pool.Get().(*Context)
h, _ := r.Find(req.Method, req.URL.Path, c)
c.reset(req, w, r.echo)
if err := h(c); err != nil {
r.echo.httpErrorHandler(err, c)
}
r.echo.pool.Put(c)
}
4,binder.go
defaultBinder实现了Bind方法,通过http方法以及header里面的ContentType来实现绑定
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
req := c.Request()
if req.ContentLength == 0 {
if req.Method == http.MethodGet || req.Method == http.MethodDelete {
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return
}
return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
}
ctype := req.Header.Get(HeaderContentType)
switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON):
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
} else if se, ok := err.(*json.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
} else {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return NewHTTPError(http.StatusBadRequest, err.Error())
}
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
} else if se, ok := err.(*xml.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
} else {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return NewHTTPError(http.StatusBadRequest, err.Error())
}
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
params, err := c.FormParams()
if err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
if err = b.bindData(i, params, "form"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
default:
return ErrUnsupportedMediaType
}
return
}
绑定数据的过程是通过reflect实现的
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(ptr).Elem()
if typ.Kind() != reflect.Struct {
return errors.New("binding element must be a struct")
}
for i := 0; i < typ.NumField(); i++ {
typeField := typ.Field(i)
structField := val.Field(i)
if !structField.CanSet() {
continue
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
if inputFieldName == "" {
inputFieldName = typeField.Name
// If tag is nil, we inspect if the field is a struct.
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
}
continue
}
}
inputValue, exists := data[inputFieldName]
if !exists {
// Go json.Unmarshal supports case insensitive binding. However the
// url params are bound case sensitive which is inconsistent. To
// fix this we must check all of the map values in a
// case-insensitive search.
inputFieldName = strings.ToLower(inputFieldName)
for k, v := range data {
if strings.ToLower(k) == inputFieldName {
inputValue = v
exists = true
break
}
}
}
if !exists {
continue
}
// Call this first, in case we're dealing with an alias to an array type
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
if err != nil {
return err
}
continue
}
numElems := len(inputValue)
if structFieldKind == reflect.Slice && numElems > 0 {
sliceOf := structField.Type().Elem().Kind()
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
}
}
val.Field(i).Set(slice)
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
return err
}
}
return nil
}
5,response.go
对http response做了一个包装
Response struct {
echo *Echo
beforeFuncs []func()
afterFuncs []func()
Writer http.ResponseWriter
Status int
Size int64
Committed bool
}
写响应头
func (r *Response) WriteHeader(code int) {
if r.Committed {
r.echo.Logger.Warn("response already committed")
return
}
for _, fn := range r.beforeFuncs {
fn()
}
r.Status = code
r.Writer.WriteHeader(code)
r.Committed = true
}
写响应body
func (r *Response) Write(b []byte) (n int, err error) {
if !r.Committed {
r.WriteHeader(http.StatusOK)
}
n, err = r.Writer.Write(b)
r.Size += int64(n)
for _, fn := range r.afterFuncs {
fn()
}
return
}
6,group.go
group主要是定义了子路由,针对大家有一个共同ns的复杂路由场景很有用
Group struct {
prefix string
middleware []MiddlewareFunc
echo *Echo
}
和普通路由的区别是path存储变成了prefix+path
// Add implements `Echo#Add()` for sub-routes within the Group.
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
// Combine into a new slice to avoid accidentally passing the same slice for
// multiple routes, which would lead to later add() calls overwriting the
// middleware from earlier calls.
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
m = append(m, g.middleware...)
m = append(m, middleware...)
return g.echo.Add(method, g.prefix+path, handler, m...)
}
7,middleware
7.1auth.go
实现了basic auth
// BasicAuthConfig defines the config for BasicAuth middleware.
BasicAuthConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Validator is a function to validate BasicAuth credentials.
// Required.
Validator BasicAuthValidator
// Realm is a string to define realm attribute of BasicAuth.
// Default value "Restricted".
Realm string
}
// BasicAuthValidator defines a function to validate BasicAuth credentials.
BasicAuthValidator func(string, string, echo.Context) (bool, error)
// BasicAuthWithConfig returns an BasicAuth middleware with config.
// See `BasicAuth()`.
func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
// Defaults
if config.Validator == nil {
panic("echo: basic-auth middleware requires a validator function")
}
if config.Skipper == nil {
config.Skipper = DefaultBasicAuthConfig.Skipper
}
if config.Realm == "" {
config.Realm = defaultRealm
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
auth := c.Request().Header.Get(echo.HeaderAuthorization)
l := len(basic)
if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
if err != nil {
return err
}
cred := string(b)
for i := 0; i < len(cred); i++ {
if cred[i] == ':' {
// Verify credentials
valid, err := config.Validator(cred[:i], cred[i+1:], c)
if err != nil {
return err
} else if valid {
return next(c)
}
break
}
}
}
realm := defaultRealm
if config.Realm != defaultRealm {
realm = strconv.Quote(config.Realm)
}
// Need to return `401` for browsers to pop-up login box.
c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
return echo.ErrUnauthorized
}
}
}
7.2compress.go
进行gzip压缩
unc Gzip() echo.MiddlewareFunc {
scheme := "gzip"
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
c.Response().Header().Add(echo.Vary, echo.AcceptEncoding)
if strings.Contains(c.Request().Header.Get(echo.AcceptEncoding), scheme) {
w := writerPool.Get().(*gzip.Writer)
w.Reset(c.Response().Writer())
defer func() {
w.Close()
writerPool.Put(w)
}()
gw := gzipWriter{Writer: w, ResponseWriter: c.Response().Writer()}
c.Response().Header().Set(echo.ContentEncoding, scheme)
c.Response().SetWriter(gw)
}
if err := h(c); err != nil {
c.Error(err)
}
return nil
}
}
}
7.3logger.go
记录请求的一些基本信息,如ip等等
func Logger() echo.MiddlewareFunc {
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
req := c.Request()
res := c.Response()
logger := c.Echo().Logger()
remoteAddr := req.RemoteAddr
if ip := req.Header.Get(echo.XRealIP); ip != "" {
remoteAddr = ip
} else if ip = req.Header.Get(echo.XForwardedFor); ip != "" {
remoteAddr = ip
} else {
remoteAddr, _, _ = net.SplitHostPort(remoteAddr)
}
start := time.Now()
if err := h(c); err != nil {
c.Error(err)
}
stop := time.Now()
method := req.Method
path := req.URL.Path
if path == "" {
path = "/"
}
size := res.Size()
n := res.Status()
code := color.Green(n)
switch {
case n >= 500:
code = color.Red(n)
case n >= 400:
code = color.Yellow(n)
case n >= 300:
code = color.Cyan(n)
}
logger.Printf(format, remoteAddr, method, path, code, stop.Sub(start), size)
return nil
}
}
}
7.4recover.go
对panic进行recover操作
func Recover() echo.MiddlewareFunc {
// TODO: Provide better stack trace `https://github.com/go-errors/errors` `https://github.com/docker/libcontainer/tree/master/stacktrace`
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
defer func() {
if err := recover(); err != nil {
trace := make([]byte, 1<<16)
n := runtime.Stack(trace, true)
c.Error(fmt.Errorf("panic recover\n %v\n stack trace %d bytes\n %s",
err, n, trace[:n]))
}
}()
return h(c)
}
}
}
推荐阅读