国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

echo源碼分析

共 29318字,需瀏覽 59分鐘

 ·

2021-05-10 17:59

web框架的核心作用有三個(gè):分層、路由、中間件。針對于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 是一個(gè)比較輕量級的框架,下面基于[email protected]對它的源碼進(jìn)行分析。

主要有下面6個(gè)文件和三個(gè)目錄組成。

binder.gocontext.goecho.gogroup.goresponse.gorouter.gomiddleware_fixturewebsite


其中middleware里面定義了最基本最常用的四個(gè)中間件

auth.gocompress.gologger.gorecover.go

_fixture是一些網(wǎng)頁資源

 % ls _fixturefavicon.ico  folder    images    index.html

website 是說明文檔,中間有個(gè)Dockerfile 可以在本地編譯鏡像,跑起來

 % ls websiteDockerfile  config.json  layoutsargo.json  content    static

首先我們看下如何使用echo

package main
import ( "net/http"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware")
func main() {  // 創(chuàng)建一個(gè)echo實(shí)例 e := echo.New()
// 注冊中間件  // 需要我們在入口文件手動(dòng)注入基礎(chǔ)中間件 e.Use(middleware.Logger()) e.Use(middleware.Recover())
  // 注冊路由 e.GET("/", hello)
  // 啟動(dòng)服務(wù) e.Logger.Fatal(e.Start(":1323"))}
// 路由handle提出來了而已// 匿名函數(shù)方式 不重要func hello(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!")}


1,echo.go文件


New函數(shù)定義在echo.go 文件里面

func New() (e *Echo) {  e = &Echo{    // 創(chuàng)建一個(gè)http Server指針    Server:    new(http.Server),    // 創(chuàng)建一個(gè)https的 Server指針    TLSServer: new(http.Server),    AutoTLSManager: autocert.Manager{      Prompt: autocert.AcceptTOS,    },    // 日志實(shí)例    Logger:   log.New("echo"),    // 控制臺、日志可以彩色輸出的實(shí)例    colorer:  color.New(),    maxParam: new(int),  }  // http server綁定實(shí)現(xiàn)了server.Handler的實(shí)例  // 也就是說Echo框架自身實(shí)現(xiàn)了http.Handler接口  e.Server.Handler = e  // https server綁定實(shí)現(xiàn)了server.Handler的實(shí)例  e.TLSServer.Handler = e  // 綁定http服務(wù)異常處理的handler  e.HTTPErrorHandler = e.DefaultHTTPErrorHandler  //   e.Binder = &DefaultBinder{}  // 設(shè)置日志輸出級別  e.Logger.SetLevel(log.ERROR)  // 綁定標(biāo)準(zhǔn)日志輸出實(shí)例  e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)  // 綁定獲取請求上下文實(shí)例的閉包  e.pool.New = func() interface{} {    return e.NewContext(nil, nil)  }  // 綁定路由實(shí)例  e.router = NewRouter(e)  // 綁定路由map  // 注意這個(gè)屬性的含義:路由分組用的,key為host,則按host分組  // 記住與Router.routes區(qū)別  // Router.routes存的路由的信息(不包含路由的handler)  e.routers = map[string]*Router{}  return}

手先初始化了一個(gè)echo對象,然后定義了一個(gè)DefaultBinder,實(shí)現(xiàn)了Binder接口,這個(gè)接口定義在bind.go文件里,后面介紹

  Binder interface {    Bind(i interface{}, c Context) error  }

接著設(shè)置了日志級別為ERROR,設(shè)置了標(biāo)準(zhǔn)輸出Logger,然后初始化了一個(gè)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文件的時(shí)候詳細(xì)講解.

其中的Map定義如下

  Map map[string]interface{}

context里面存儲了

r *http.Request, w http.ResponseWriter

這也就是為什么寫echo的controller的時(shí)候只用傳 context對象就行,而標(biāo)準(zhǔn)的http包需要頂替包含參數(shù)r *http.Request, w http.ResponseWriter的HandleFunc

最后初始化了路由(router.go)里面實(shí)現(xiàn)的

func NewRouter(e *Echo) *Router {  // 初始化Router  return &Router{    // 路由樹    // 路由的信息(包含路由的handler)    // 查找路由用的LCP (最長公共前綴)算法    tree: &node{      // 節(jié)點(diǎn)對應(yīng)的不同http method的handler      methodHandler: new(methodHandler),    },    // Router.routes存的路由的信息(不包含路由的handler)    routes: map[string]*Route{},    // 框架實(shí)例自身    echo:   e,  }

Router的定義如下

  Router struct {    tree   *node    routes map[string]*Route    echo   *Echo  }

是一個(gè)棵多叉樹,其中tree存儲了當(dāng)前節(jié)點(diǎn)里面的值,它的定義如下

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

我們可以看到,它的內(nèi)部調(diào)用了Add方法,其實(shí)POST、PATCH、DELETE等http method類似都是對Add方法進(jìn)行了包裹

Add方法的參數(shù)有http請求的方法,請求路徑,對應(yīng)的處理函數(shù)和中間件參數(shù)。

func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {  // 獲取handler的名稱  // ??這個(gè)方法里面盡然用了反射獲取name 只是個(gè)name有必要么 沒別的辦法了嗎?  name := handlerName(handler)  // 注冊路由  // 注意第三個(gè)參數(shù)是個(gè)閉包 匹配到路由就會執(zhí)行這個(gè)閉包  e.router.Add(method, path, func(c Context) error {    h := handler    // Chain middleware    for i := len(middleware) - 1; i >= 0; i-- {       // 注意這里的中間件是這個(gè)路由專屬的      // 而Use、Pre注冊的中間件是全局公共的      // 遍歷中間件      // 注意返回值類型是HandlerFunc      //典型的洋蔥模式      h = middleware[i](h)    }     // 執(zhí)行最后一個(gè)中間件    return h(c)  })
  // 本次注冊進(jìn)來的路由的信息,只存放了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函數(shù)

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  // 設(shè)置框架實(shí)例到http server的Handler  // Echo框架結(jié)構(gòu)體實(shí)現(xiàn)了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 {     // 監(jiān)聽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())) } // 啟動(dòng)http server return s.Serve(e.Listener) } if e.TLSListener == nil {     // 設(shè)置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)}

接下來的流程就是標(biāo)準(zhǔn)httpserver的執(zhí)行流程

s.Serve()??// accept網(wǎng)絡(luò)請求rw, e := l.Accept()??// goroutine處理請求go c.serve(ctx)??// 執(zhí)行serverHandler的ServeHTTPserverHandler{c.server}.ServeHTTP(w, w.req)??// 執(zhí)行當(dāng)前框架實(shí)例的ServeHTTP方法handler.ServeHTTP(rw, req)

看下echo是怎么實(shí)現(xiàn)ServeHTTP方法的

// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {  // Acquire context  // 獲取上下文實(shí)例  c := e.pool.Get().(*context)  c.Reset(r, w)
h := NotFoundHandler // 不存在預(yù)執(zhí)行中間件時(shí) // 說說這個(gè)預(yù)執(zhí)行中間件的含義: // 看源碼注釋的含義是在尋找到路由之前執(zhí)行的中間件 // 簡單來說和普通中間件的的區(qū)別就是,還沒走到匹配路由的邏輯就會執(zhí)行的中間件,從下面來看只是代碼邏輯的區(qū)別,實(shí)際的中間件執(zhí)行順序還是誰先注冊誰先執(zhí)行。所以無論是存在普通中間件還是預(yù)執(zhí)行中間件,路由的handle總是最后執(zhí)行。 // 個(gè)人感覺預(yù)執(zhí)行中間件的意義不大 if e.premiddleware == nil {    // 先找當(dāng)前host組的router // LCP算法尋找當(dāng)前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 {     // 看見這個(gè)預(yù)執(zhí)行中間件的區(qū)別了吧 // 把注冊普通中間件的邏輯又包裝成了一個(gè)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 // 執(zhí)行中間件鏈 // 在applyMiddleware中所有中間件構(gòu)成了一個(gè)鏈 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 typesconst (  MIMEApplicationJSON                  = "application/json"  MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + charsetUTF8
// Headersconst (  HeaderAccept              = "Accept"  HeaderAcceptEncoding      = "Accept-Encoding"
// Errorsvar (  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 {}

和對應(yīng)的對象

  context struct {    request  *http.Request    response *Response    path     string    pnames   []string    pvalues  []string    query    url.Values    handler  HandlerFunc    store    Map    echo     *Echo  }

獲取參數(shù)值

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

參數(shù)綁定

func (c *context) Bind(i interface{}) error {  return c.echo.Binder.Bind(i, c)}

參數(shù)校驗(yàn)

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的一系列結(jié)構(gòu)體

  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  }

初始化一個(gè)router對象

func NewRouter(e *Echo) *Router {  return &Router{    tree: &node{      methodHandler: new(methodHandler),    },    routes: []Route{},    echo:   e,  }}

添加路由,構(gòu)建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      //提取路由中的參數(shù)前面部分,構(gòu)建樹 r.insert(method, path[:i], nil, skind, "", nil, e) for ; i < l && path[i] != '/'; i++ { }      //把參數(shù)名放到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)}

提取參數(shù)和正則以后,構(gòu)建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方法,這個(gè)方法是在echo的同名方法里,把router轉(zhuǎn)化成handleFunc,然后調(diào)用的。

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實(shí)現(xiàn)了Bind方法,通過http方法以及header里面的ContentType來實(shí)現(xiàn)綁定

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}

綁定數(shù)據(jù)的過程是通過reflect實(shí)現(xiàn)的

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做了一個(gè)包裝

  Response struct {    echo        *Echo    beforeFuncs []func()    afterFuncs  []func()    Writer      http.ResponseWriter    Status      int    Size        int64    Committed   bool  }

寫響應(yīng)頭

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}

寫響應(yīng)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主要是定義了子路由,針對大家有一個(gè)共同ns的復(fù)雜路由場景很有用

  Group struct {    prefix     string    middleware []MiddlewareFunc    echo       *Echo  }

和普通路由的區(qū)別是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

實(shí)現(xiàn)了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

進(jìn)行g(shù)zip壓縮

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進(jìn)行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)    }  }}


推薦閱讀


福利

我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

瀏覽 80
點(diǎn)贊
評論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評論
圖片
表情
推薦
點(diǎn)贊
評論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 亚洲AV无码成人精品区久| 久久久久久高清毛片一级| 一区二区三级片| 中文字幕在线无码观看| 国产二区视频| 在线成人一区二区| 黄色福利网站| 亚洲v视频| 欧美午夜三级| 亚洲精品久久久久久久久久久 | 大鸡巴视频在线观看| 国产又大又黄| 亚洲无码图片| 翔田千里无码AV在线观看| 97精品人妻一区二区三区香蕉农| 欧美精品在线免费| 亚洲色在线视频| AV解说| 成人视频一区二区三区| 18禁网站禁片免费观看| 人人草人人摸人人看| 日韩五码在线| 久久久波多野结衣| 亚洲成人高清在线| 亚洲免费观看在线观看| 亚洲成人动漫在线| 无码成人毛片| 亚洲中文字幕人妻。| 亚欧无码| 欧美成人网站在线观看| 久久久福利| 国产婷婷色一区二区在线观看| 男女拍拍免费视频| 欧美色插| 西西特级WWW444无码| 青青草逼视频| 国产精品同| 99色热视频| 丰满人妻一区二区三区精品高清 | 亚洲三级精品| 九九九九精品视频| 成人a一级片| 土耳其电影《爱与罚》| 波多野结衣毛片| 香蕉综合网| 翔田千里无码XXXXXX| 久久艹视频| 精品乱子伦一区二区三区免费播成| 久久精品91| 日本大香蕉伊人| 国产又猛又黄又爽| 日韩在线免费播放| 免费在线看A| 91精品青青草| 北条麻妃久久久| 久久久久久久久久久久国产精品| 成人黄色一级A片| 强开小嫩苞毛片一二三区| 丁香五月天婷婷久久| 国产A级成人婬片1976| 国产无码在线影院| 美日韩三级| 亚洲精品一区二区二区的游戏情况| 大香蕉一区二区| 国产69精品久久久久久久久久久久 | 久久久久久97电影院电影院无码| 婷婷五月天激情俺来也| 久久一做爱| 国产激情视频| 九九热毛片在线观看| 上海熟搡BBB搡BBBB| www.sesese| 黄色网页在线观看| 91白浆| 久久机热| 国产一区二区三区在线| 青草网| 91熟女丰满原味| 三洞齐开Av在线免费观看| 亚洲欧美综合| 亚洲AV成人片色在线观看麻豆| 女侠吕四娘第二部| 亚洲国产精品成人综合色五月| 日韩精品高清中文| 91大神免费在线观看| 高清无码学生妹| 中文字幕免费久久| 99成人精品| 欧美精产国品一| 欧美成人精品A片免费一区99| 猫咪亚洲AV成人无码电影| www.伊人大香蕉| 日韩欧美性爱网站| 亚洲日韩精品欧美一区二区yw| 国产一级乱伦| 一道本无码视频| 狠狠操免费| 操b视频在线播放| 中文在线A∨在线| 波多野结衣在线观看一区二区| 日韩成人免费观看| 无码无码一区二区三区| 少妇推油呻吟白浆啪啪成人片| 日韩精品免费无码视频| 超碰人人在线观看| 久久久久久久网站| 亚洲无码99| 国产一卡二卡三卡| 黄色片网站在线观看| 亚洲在线播放| 大乳奶一级婬片A片| 色婷婷五月天| 黑人无码一二三四五区| 欧美成人中文字幕| 霸道总裁雷总各种姿势白浆爱情岛论坛| 亚洲婷婷视频| 另类老妇极品BBWBBw| 欧美三p| 三级黄色视频在线观看| AAA无码| 日本操b| 干欧美| 91免费视频在线| 国产亚洲色婷婷久久99精品91| 一级黄色A片| 黃色A片一級二級三級免費久久久| 波多野结衣av在线观看窜天猴| 人人人妻人人人操| 日本中文字幕在线免费观看| 亚洲色图1| 91精品国产一区二区| 成年人在线观看视频网站| 成人无码交配视频国产网站| 中文字幕第98页| 欧美操逼逼| 欧美性国产| 亚洲欧洲免费看| 我要看黄色一级片| 色欧美视频| 五月婷婷婷婷| 日韩午夜福利| 蜜桃传媒一区二区亚洲AV| 久热免费视频| 俺来也俺去啦欧美www| 青青草国产在线视频| 日韩不卡视频在线观看| 蜜臀网| 午夜福利视频网| av网站免费观看| 男女无码| 人妻人人操| 国产日本在线视频| av电影在线免费观看| 亚洲福利免费观看| 91热久久| 亚洲精品成人7777777| 无码三级在线观看| 动图综合亚洲综合欧美男男| 日本一级黄色电影网| 18禁网站在线播放| 国产黄色小视频在线观看| 神马影院午夜福利| 九九国产精品| 大香蕉AV在线| 午夜免费AV| 深爱激情五月婷婷| 九九九九国产| 欧美成人在线网站| 国产无码专区| 日韩精品成人AV| 中国12一13毛片| 一级a免一级a做片免费| 国产精品a久久久久| 国产精品一区二区三| 在线播放中文字幕| 亚洲日日干| 亚洲天天在线| 欧美一区二区三区不卡| 一二三四在线视频| 亚洲无码影视| 日韩成人视频在线观看| 日本天堂Tv视频在线观看| 91精品国产乱码久久久| 插插插综合| 久草国产精品| 51妺嘿嘿午夜福利| 最近中文字幕高清2019中文字幕| 亚洲无码一区二区三区蜜桃| 人妻在线免费视频| 91丨熟女丨对白| 久久久亚洲| 悠悠无码一区日韩妇女| 天堂中文在线a| 免费黄片在线| 午夜福利剧场| 超碰9| 91免费在线视频观看| AAAA毛片视频| 狠狠AV| 中文字幕北条麻妃| 99色99| 国产精品主播| 麻豆性爱视频| 伊人东京热| 亚洲成人在线视频| 韩国一区二区三区在线观看| 免费A级黄片| 蜜桃视频网址| 无码免费视频在线观看| 综合网伊人| 国产人与禽zoz0性伦| 超碰97成人| 福利无码| 日本A级毛片| 青草免费视频| 狼人狠干| av无码毛片| 国内精品久久久久久久| 97人人插| 91大神在线免费看| 1024大香蕉| 迷情校园综合| 日韩免费三级片| 无码高清在线播放| www人人操| 国产91无码| 黄片免费视频观看| 江苏妇搡BBBB搡BBB| 日本少妇黄色视频| 日韩性爱在线视频| 国产一区二区三区免费观看| 国产三级在线观看视频| 91工厂露脸熟女| 国产在线成人视频| 国产一区二区不卡亚洲涩情| 91成人无码看片在线观看网址| 日本大香蕉视频| 久草黄色电影| 91久久久久久久久久久久18| 亚洲人妻一区二区| 九九热精品视频| 91香蕉国产成人App| 偷拍亚洲色图| 综合在线视频| 日本少妇视频| 337P大胆粉嫩银噜噜噜| 午夜福利小视频| 人人摸人人干人人操| 99青草在线视频| 欧美日韩成人网站| 亚洲成人一级片| 三级片中文字幕| 欧美熟女性爱视频| 黄色动漫在线免费观看| 丁香六月综合| 欧美日本成人网站入口| 97视频网站| 国产一级二级在线观看| 五月丁香婷中文| 中文字幕无码视频| 男人午夜网站| 日韩欧美精品一区二区| 日本免费一区二区三区| 99导航| 日本电影一区二区| 东京热AV在线| 欧美久久一区二区三区四区视频| 狠狠热视频| 国产免费a| av在线资源| 黄色视频大全在线观看| 亚洲区成人777777精品| 欧美不卡视频| 国产女人18水真多18精品| 69AV视频在线观看| 天天做天天爱天天高潮| 大香焦伊人国产| 大鸡巴免费视频| 狠狠操在线视频| 美女中文字幕| 爆乳一区二区三区| 久久精品一区二区三区四区| 国产精品2025| JlZZJLZZ亚洲美女18| 色噜噜狠狠色综无码久久合欧美| 国产女人18毛片水18精品软件| 亚洲高清无码视频大全| 99热青青草| 91精品人妻一区二区| 中文字幕乱码视频| 日韩午夜福利| 欧美18禁黄免费网站| 欧美性生交18XXXXX无码| 欧美成人午夜| 人妻无码一区二区三区免费| 男人天堂免费视频| 国产女人在线观看| 最美人妖系列国产Ts涵涵| 免费欧美性爱| 青草免费视频| 色老板综合| 欧美三级大片| 欧美操女人| 色婷婷久综合久久一本国产AV| 亚洲精品一区二三区不卡| 无码不卡一区| 中文字幕在线观看视频免费| 亚洲天堂自拍| 国产成人自拍网| 天天色影| 中文无码人妻少妇| 性爱日韩| 亚洲精品自拍| 色视频网| 国产学生妹在线播放| 久精品视频| 日韩欧美成人网| 国产17c精品视频一二三区| 91日综合欧美| 日韩一级片免费| 欧美成人高清| 人妻国产| 丁香花小说完整视频免费观看| 日日夜夜天天操| 伊人免费在线| 51乱伦| 亚洲黄色视频在线观看网站| 88AV在线| 亲子乱AV一区二区| 日日夜夜拍| 亚洲av不卡| 欧美国产乱伦| 久久婷婷视频| 国产精品成人在线视频| 午夜成人av| 国产黄色直播| 视频二区中文字幕| 国产最新在线视频| 天天高清无码| 亚洲秘AV无码一区二区qq群| 午夜福利视频网站| 在线视频中文字幕| 俺去啦俺去啦| 黄片一区二区| 精品热99| 国产无码中文字幕| 欧美视频色| 久久这里精品| 久久午夜无码鲁片午夜精品男男| 日韩A片在线| 69视频在线观看| 日本暖暖视频| 免费观看无码视频| www.xxx国产| 特级西西人体大胆无码| 91在线观看网站| 99色热| 欧美激情一区二区A片成人牛牛| 成人亚洲AV日韩AV无码| 51精品国产午夜福利| 国产一卡二卡在线观看| 色99在线视频| 久久久精品亚洲| 91精品人妻人人爽| 欧美黄色网址| 特一级黄色电影| 亚洲春色一区二区三区| 欧美日韩中字| 在线你懂得| 你懂的在线视频观看| 九九热在线精品| 天天日夜| 无码人妻精品一区二区三区99仓| 少妇搡BBBB搡BBB搡毛片| 丝袜足交在线| 久久久精品999| 亚洲色一区二区| 日本黄色视频电影| 亚洲女人在线| 大鸡巴影院| 大地影院资源官网| 亚洲视频综合网| 国产精品一区二区性色AV| 欧美特黄AAA| 高潮无码视频| 亚洲AV永久无码国产精品久久| 亚洲色情在线观看| 日韩色爱| 国产又爽又黄免费网站校园里| 国产精品小电影| 欧美性爱小说| 精品国内自产拍在线观看视频| 69视频在线观看| 爱爱爱爱视频| 翔田千里无码精品| 麻豆传媒电影| 蜜芽av在线观看| 五月婷中文字幕| 中文字幕亚洲人妻| 亚洲性爱中文字幕| 欧美一级免费A片| 国产精品婷婷| 成人免费精品视频| 女人18特级毛片。| 美女自慰网站免费| 操逼色| 久久99精品久久久水蜜桃| 先锋影音男人资源站| 国产精品一区二区三| 蜜桃无码视频小说网站| 久久午夜无码鲁丝片| 精品视频免费在线| 色妹子综合| 色吊丝中文字幕| 成人性爱福利视频| 麻豆视频一区| 欧美激情亚洲无码| 久久99免费视频| 中文字幕超清在线观看| 欧美在线观看视频| 男女日皮的视频| 成人午夜视频精品一区| 四虎黄色网址| 中文在线A∨在线| 99热1| 亚洲中文字幕在线视频观看| 91吊逼| 国产精品秘麻豆免费版现看视频 | 国产在线资源| 天天爱av| 欧美激情四射老司机| 日韩激情网站| 日韩色图在线观看| 日韩av免费| 人妻AV一区| 日日操天天操夜夜操| AV不卡在线| 黄色一级小说| 国产黄色视频网站| 无码av一区| 97精产国品久久蜜桃臀| 亚洲av| 午夜传媒一区二区三区| 免费毛片网站| 九九精品视频在线观看| 国产日韩在线播放| 免费无码在线观看| 99热国产在线观看| 四川妇搡BBBB搡BBBB| 国产又大又粗又长| 偷拍亚洲色图| 国产亚洲视频在线观看视频| 99热在线免费| 日本一级特级毛片视频| 亚洲在线| 北条麻妃无码精品| 在线观看中文字幕一区| 无码高清在线观看| avwww| 亚洲一区二区视频在线观看 | 丁香五月天婷婷久久| 日韩在线不卡视频| 激情久久av| 強暴人妻一区二区三区| 西西人体大胆ww4444| 九九热视频在线| 91双飞会所双飞在线| 一级欧美视频| 伊人黄色片| 国产91探花系列在线观看| 久久综合中文| 尤物yw| 色欧美亚洲| 俺来俺去| 天天艹天天干| 日韩国产欧美精品一区| 国产精品无码天天爽视频| 国产精品在线看| 欧美日韩国产尤物主播精品| 免费黄片网站在线观看| sm国产在线调教视频| 色婷婷黄色| 亚洲精品无码久久| 中文字幕A片| 欧美一级特黄AAAAAA片在线视频| 日韩毛| AV无码免费一区二区三区不卡| 桃色Av| 国内精品一区二区| 视频一区二区三区在线观看| 成人777777免费视频色| 私人玩物』黑絲OL尤物| 国产人人看| aaaaaa在线观看免费高清| 五月天堂婷婷| 欧美成人视频在线观看| 国产免费a片| 黄片网站在线免费观看| 最美孕交vivoestv另类| 西西444WWW大胆无视频软件亮点 | 艹逼电影| 日韩人妻一区二区三区| 欧美第一色| 久久福利电影| A一级横色大片| 日韩精品视频在线免费观看| 亚洲激情无码视频| 又爽又黄免费网站97双女| 五月天黄色电影| 六月婷婷久久| 国产精品午夜在线| 北条麻妃久久视频在线播放| 美日韩无码| 日韩AV成人电影| 欧美一级A片免费看视频小说| 久久6| 97精品在线| 九九自拍视频| 超碰人人人| 国产精品秘久久久久久网站| 人妻FrXXeeXXee护士| 91AV在线电影| 色操逼网| 国产精品超碰| 黄片高清| 亚洲AV无码成人精品区大猫| japanese在线观看| 伊人久久大香蕉视频| 91水蜜桃| 成人国产AV| 日韩h视频| 亚洲AV无码乱码国产| 在线观看免费欧美操逼视频| 青青草手机视频| 欧美成人手机在线| 国产在线观看不卡| 围内精品久久久久久久久白丝制服| 一级黄色蜜芽视频| 亚洲AV第二区国产精品| 天堂久草| 国产精品永久久久久久久久久 | 亚洲毛片网| 熟女综合网| 逼逼AV| 午夜福利电影无码| 国产成人亚洲日韩| 无码福利电影| 好男人WWW一区二区三区| 日韩群交视频| 国产欧美综合一区| 婷婷色777777| 在线观看高清无码视频| 国内成人AV| 操嫩逼视频| 1024大香蕉| 国产精品久久久久久久久久二区三区| 欧美性爱小说| 久久久久亚洲AV成人网人人软件| 丁香五月激情啪啪啪| 亚洲无码乱码精品| 水蜜桃成人在线| 黄色A片约| 中文字幕一区二区三区精华液| 五月天激情综合网| 国内自拍偷拍视频| 搡BBB搡BBBB搡BBBB' | 欧美少妇做爱| 学生妹一级| 三级片青青草| 亚洲中文无码在线| 亚洲日韩在线观看视频| av在线免费观看网站| 特级毛片AAAAAA蜜桃| 精品久| 亚洲综合激情| 黑人操白人| 一级片视频在线观看| 欧美一级AA| 午夜福利AV在线| 国产成人秘在线观看免费网站 | 成人三级片视频| 亚洲激情网址| 国产一级视频| 你懂的视频在线观看| 99精品在线免费观看| 中文字幕在线日亚洲9| 蜜臀99久久精品久久久懂爱| 日本内射在线播放| 超碰青青青| 免费看污网站| 国产黄色直播| 亚洲北条麻妃一级A片| Al激情欧美| 91国产精品| 嫩BBB槡BBBB槡BBB| 国产成人免费在线观看| 亚洲精品无码电影| 九九色| 日韩大屌| 亚洲视频在线免费看| 国产69久久精品成人看| 欧美综合婷婷| 最新中文| 国产suv精品一区二区6精华液| 日本不卡在线观看| 四川少妇搡bbbb搡bbbb| 三级片在线看片AV| 日韩黄色片| 亚洲精品一区二区三区在线观看| 亚洲三级免费| 99人妻人人爽人人添人人精品 | 殴美亚洲一流| 日本a视频| 国产免费无码| 成人午夜免费视频| 老司机免费视频| 男插女青青影院| 91人人妻人人澡人人爽| 男人的天堂视频网站| 精品欧美视频| 91探花秘在线播放偷拍| 一区二区在线看| 喷水视频在线观看| 日韩一区不卡| 草逼视频网站| 日本免费不卡视频| 成人在线第一页| 国产成人精品免高潮在线观看| 人妻中文字幕av| 91ThePorn国产| 18禁成人A∨片| 丁香六月婷婷激情| 亚洲一级婬片A片AAAA网址| h在线观看h| 俺来也影院| 人妻精品一区二区在线| 精品福利一区二区三区| 米奇7777狠狠狠狠| 91蝌蚪久久| 暗呦罗莉精品一区二区| 4虎亚洲人成人网www| 天天影视综合网免费观看电视剧国产| 久久V| 日韩无码高清视频| 亚洲成人在线播放| 91成人片| 亚洲性爱网站| 日韩在线一区二区三区| 亚洲字幕| 亚洲欧美日韩黑料吃瓜在线观看| 黄色内射在线播放| 亚洲欧美日韩黑料吃瓜在线观看 | 一区二区三区无码精品| 免费观看一区| 色777色| 91热爆TS人妖系列| 波多野结衣av在线观看| 成人网站在线看。| 香蕉操逼| 北条麻妃JUX-869无码播放| 三级成人在线| 人人妻人人超| 夜夜爽妓女77777毛片A片| 国产69精品久久久久久| 中文字幕精品人妻| 亚洲成人色色| 内射在线| 骚逼操| 伊人天天干| 日本不卡一区二区三区| 蜜臀91| 日本精品人妻无码77777| 无码内射视频| 亚洲无码免费| 中文字幕在线看成人电影| 日韩中文字| 91久久久裸身美女| 91久久久久久久91| 国内精品人妻无码久久久影院蜜桃| 无码三级在线播放| 日韩一区二区三区四区久久久精品有吗 | 日本乱伦网站| 黄色电影视频网站| 免费看一级黄色片| 69AV在线播放| 91操B| 一级A片60分钟免费看| 久久av一区二区三区| 人妻无码中文字幕免费视频蜜桃| 91探花秘在线播放| 久久毛久久久j| 韩国午夜福利视频| 高清无码中文字| 中文在线第一页| 一边做一边说国语对白| 日韩无码视频观看| 91视频你懂的| 囯产精品一区二区三区线一牛影视1| 在线视频日本| 大鸡巴操骚逼视频| 黄色资源在线观看| 日本中文在线| 日韩在线| 亚洲国际中文字幕在线| 一道本不卡视频| 91丨精品丨国产丨丝袜| 九九精品网| 国产欧美日韩在线视频| 三级片一区| 人人摸人人色| 在线观看黄网| 日韩免费视频在线观看| 青青草五月天色婷婷丁香| 北条麻妃无码一区三区| 大香蕉精品一区| 亚洲Av秘无码一区二区| 最近最经典中文MV字幕| 大地资源38页| 亚洲欧洲精品在线| 久久综合电影| 国产黄色视频在线| 2015中文字幕黄色视频| 韩日高清无码| 欧美日韩美女| 少妇A片| 成年人免费电影| 肉乳无码A片av| 国产高清精品软件丝瓜软件 | 天天射天天射| 成人做爰黄AAA片免费直播岛国| 大肉大捧一进一出两腿| 亚洲中文字幕在线观看视频| 无码国产传媒精品一区| 婷婷国产| 蜜臀网| 人妻人人骑| 中文字幕视频在线播放| 久久久九九九| 少妇被躁到高潮无码| 爆菊花综合网| 草草影院第一页YYCCC| 俄罗斯白嫩BBwBBwBBw91| wwwAV| 影音先锋男人资源站| 国产欧美在线观看| 日韩不卡精品| 亚洲三级视频在线播出| 亚洲无码高清在线视频| 超小超嫩国产合集六部| 久久男人天堂| 黄色在线免费看| 国产操屄网| 天天逼网| 日产精品久久久一区二区| 91ThePorn国产在线观看| 久色网站| 丁香花在线小说免费全文| 性欧美XXXX| 91无码精品国产AⅤ| www九九九| 亚洲综合另类| 亚洲中文在线播放| 久久精品99视频| 不卡无码中文字幕| 亚洲AV秘成人久久无码海归| 欧美精品成人在线| 91综合视频| 日本高清版色视频| 西西888WWW大胆视频| 国产乱子伦一区二区三区免看| 国产熟女| 亚洲无码在线播放| 黄色片免费看| 好爽~要尿了~要喷了~同桌| sm国产在线调教视频| 51国产黑料吃瓜在线入口| 人人澡人人澡人人澡| 爱爱导航| 日皮视频在线观看免费| 欧美日韩在线视频免费| 精品久久成人| 国产成人AV片| 竹菊影视一区二区三区| 国产日韩一区二区三免费高清| 久久精品视频一区| 国产精品久久精品| 久热大香蕉| 麻豆18禁| 黄色电影网页| 亚洲自拍无码| 精品二区| 免费看特别黄色视频| 超清无码在线| 亚洲国产成人91PORN| 91偷拍网| 999精品视频在线| 精品国产一级| 国产超碰| 久操视频免费观看| 欧美不卡在线播放| 99re在线精品| 日韩在线播放视频| 亚洲一级A片| 成人午夜A片| 国产成人秘在线观看免费网站| 中文字幕欧美视频| 久操国产| 国产一级特黄aaa大片| 秋霞欧美在线| 中国免费一级无码成人片| 婷婷日韩一区二区三区| 色婷婷久久久久swag精品| 精品一区二区三区四区五区| 中文字幕无码日韩| 国产精品成人AV在线| 四川搡BBBBB搡BBB| 97成人在线视频| 青娱乐欧美| 日韩A级片| 亚洲中文字幕免费观看视频| 喷水视频在线观看| 久久久久久成人电影| 国产18欠欠欠一区二区| 日韩欧美人妻无码精品| A片在线观看免费| 欧美在线天堂| 国产无遮挡又黄又爽又| 北条麻妃JUX-869无码播放 | 欧美三级在线| 欧美老妇另类BBwBBw| 男人的天堂社区| 黄片一区二区| 亚洲av无码精品| 国产又粗又猛又爽又黄91精品| 91操B| 蜜臀AV一区二区三区免费看| 超碰碰97| 国产精品欧美一区二区三区苍井空| 久久青青视频| 北条麻妃久久视频在线播放| 韩国gogogo高清在线完整版| 欧美成人午夜福利| 思思热在线| 日韩午夜无码| 日韩视频――中文字幕| 国产高清不卡| 男人操女人网站| 自拍偷拍影音先锋| 亚洲国产高清无码| 中文字幕永久在线视频v1.0| 自拍偷拍图区| 久久免费视频观看| 精品一区二区三区毛片| 特一级黄片| av资源在线播放| 健身房被教练3p喷水了| 中文字幕日本成人| 豆花视频logo进入官网| 2019中文字幕在线免费观看| TheAV精尽人亡av| 婷婷91| 夜夜爱爱| 国产八区| 成人久久视频| 天堂色| 精品国产毛片| a天堂视频| 美日韩A片| 四川少妇bbbbbbbbb| 国产精品一二三区夜夜躁| 97视频福利| 国产黄色免费电影| 黄色视频网站免费观看| 中文字幕亚洲人妻| 嫩草视频网站| 在线观看国产| 黄色视频网站在线播放| 91丨国产丨熟女熟女| 中文字幕操逼网站| 国产精品美女在线观看| AV三级片在线观看| 婷婷色情网| 大香蕉大香蕉视频网| 久久这里只有| 日韩AV网站在线观看| 青青青国产| 亚洲无码99| 亚洲中文字幕av天堂| 五月激情六月婷婷| 91av免费观看| 91麻豆天美传媒在线| 午夜精品人妻无码| 久久h| 极品久久久久| 国产精品久久久久久久久夜色| 色色色色色色色色欧美| 亚洲成人av无码| 日韩一级片视频| 婷婷日逼| 欧美啪啪啪|