精华内容
下载资源
问答
  • Gin实战演练

    2021-03-21 22:48:29
    Gin实战演练 1 gin的简单使用 package main import "github.com/gin-gonic/gin" func main() { // Default方法的主要作用是实例化一个带有日志、故障恢复中间件的引擎。 r := gin.Default() //实例化一个gin对象...

    Gin实战演练

    1 gin的简单使用

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
       // Default方法的主要作用是实例化一个带有日志、故障恢复中间件的引擎。
       r := gin.Default() //实例化一个gin对象
       // 定义请求
       //定义一个GET请求的路由,参数一是路由地址,也就是在浏览器访问的相对路径,
       //                     参数二是一个匿名函数,函数内部用于业务逻辑处理。
       r.GET("/login", func(c *gin.Context) {
          c.JSON(200, gin.H{ //JSON内容可以通过gin提供的H方法来构建,非常方便。
             "msg": "login", //调用JSON方法返回数据。JSON的操作非常简单,参数一是状态码,参数二是JSON的内容。
          })
       })
       // Run方法最终会调用内置http库的ListenAndServe方法来监听端口,如果不传参数默认监听80端口,
       // 也可以通过参数来变更地址和端口。
       r.Run(":12005")
    }
    

    2 RESTful API

    RESTful 是⽹络应⽤程序的⼀种设计⻛格和开发⽅式,每⼀个URI代表⼀种资源,客户端通过 POST 、 DELETE 、 PUT 、 GET 四种请求⽅式来对资源做增删改查的操作。

    同样的,Gin框架给我们提供的除这4种动词外,还有 PATCH 、 OPTION 、 HEAD 等,详细内容可以查看 rentergroup.go ⽂件的IRoutes接⼝

    // IRoutes defines all router handle interface.
    type IRoutes interface {
       Use(...HandlerFunc) IRoutes
    
       Handle(string, string, ...HandlerFunc) IRoutes
       Any(string, ...HandlerFunc) IRoutes
       GET(string, ...HandlerFunc) IRoutes
       POST(string, ...HandlerFunc) IRoutes
       DELETE(string, ...HandlerFunc) IRoutes
       PATCH(string, ...HandlerFunc) IRoutes
       PUT(string, ...HandlerFunc) IRoutes
       OPTIONS(string, ...HandlerFunc) IRoutes
       HEAD(string, ...HandlerFunc) IRoutes
    
       StaticFile(string, string) IRoutes
       Static(string, string) IRoutes
       StaticFS(string, http.FileSystem) IRoutes
    }
    

    例如接口:

    func main() {
       router := gin.Default()
       // 请求动词的第一个参数是请求路径,第二个参数是用于逻辑处理的函数
       router.POST("/article", func(c *gin.Context) {
          c.String(200, "article post")
       })
       router.DELETE("/article", func(c *gin.Context) {
          c.String(200, "article delete")
       })
        
        router.GET("/article/:id/:action", func(c *gin.Context) {
    		id := c.Param("id")
    		action := c.Param("action")
    		fmt.Printf("2 /article/:id->%s, action:%s\n", id, action)
    		c.String(200, id+" "+action)
    	})
    
    	router.Run(":8080")
    }
    
    • 通过web访问url

    • 使用curl命令来访问url

      / 测试方法
      // curl -X PUT http://localhost:8080/article
      // curl -X POST http://localhost:8080/article
      // curl -X GET http://localhost:8080/article
      // curl -X DELETE http://localhost:8080/article
      

    路由参数

    :路由

    这种匹配模式是精确匹配的,只能匹配⼀个

    访问:http://localhost:8080/users/123

    输出:123

    func main() {
       r := gin.Default()
       r.GET("/users/:id", func(c *gin.Context) {
          id := c.Param("id")
          c.String(200, "The user id is  %s", id)
       })
       r.Run(":8080")
    }
    

    *路由

    还有⼀种不常⽤的就是 * 号类型的参数,表示匹配所有,结果是⼀个 / 开头的路径字符串

    访问:http://localhost:8080/users/123

    输出:/123

    func main() {
       r := gin.Default()
       r.GET("/users/*id", func(c *gin.Context) {
          id := c.Param("id")
          c.String(200, "The user id is  %s", id)
       })
       r.Run(":8080")
    }
    

    特别说明⼀点

    访问 http://localhost:8080/users时候,会被重定向到 http://localhost:8080/users/,根本原因在于 /users 没有匹配的路由,但是有匹配 /users/ 的路由,所以就会被重定向 到 /users/ ,如下:

    func main() {
       r := gin.Default()
       r.GET("/users/*id", func(c *gin.Context) {
          id := c.Param("id")
          c.String(200, "The user id is  %s", id)
       })
    }
    

    禁止重定向

    r.RedirectTrailingSlash = false
    

    加上如上设置之后,访问 http://localhost:8080/users,是访问不成功的,因为没有服务器去处理这个url

    3 Gin获取查询参数

    例如:

    http://127.0.0.1:8080/users?k1=v1&k2=v2
    

    以 ? 为起点,后⾯的 k=v&k1=v1&k2=v2 这样的字符串就是查询参数

    上述案例中有2个参数键值对,通过&来连接:

    k1=v1
    k2=v2
    

    可以使用gin框架中的如下接口来获取实际的参数值

    // 3-2-url-param.go url参数获取
    package main
    
    import (
       "fmt"
    
       "github.com/gin-gonic/gin"
    )
    
    func main() {
       r := gin.Default()
       r.GET("/", func(c *gin.Context) {
          c.DefaultQuery("id", "0")
          value, ok := c.GetQuery("id") // 适合用来判断是否存在该参数
    
          if ok {
             fmt.Println("id:", value)
          } else {
             fmt.Println("id: nil")
          }
    
          c.String(200, c.DefaultQuery("wechat", "default baidu_org"))
       })
       r.Run(":8080")
    }
    

    实际GetQuery具体实现:

    func (c *Context) GetQuery(key string) (string, bool) {
       if values, ok := c.GetQueryArray(key); ok {
          return values[0], ok
       }
       return "", false
    }
    

    DefaultQuery的具体实现也是调用GetQuery:

    func (c *Context) DefaultQuery(key, defaultValue string) string {
       if value, ok := c.GetQuery(key); ok {
          return value
       }
       return defaultValue
    }
    

    GetQuery 和 Query的区别

    GetQuery中传入key值,会返回value,ok 若ok为true ,则value 有值

    Query是直接返回字符串

    可以⽤ GetQuery 来代替 Query ⽅法。 GetQuery ⽅法的底层实现其实是 c.Request.URL.Query().Get(key) ,通过 url.URL.Query() 来获取所有的参数键值对

    仔细看GetQuery的具体使用方式

    //本质上是调⽤的GetQueryArray,取的数组中第⼀个值
    func (c *Context) GetQuery(key string) (string, bool) {
    	if values, ok := c.GetQueryArray(key); ok {
    		return values[0], ok
    	}
    	return "", false
    }
    
    func (c *Context) GetQueryArray(key string) ([]string, bool) {
    	c.getQueryCache()  //得到缓存,这一点很关键,缓存所有的键值对
    	if values, ok := c.queryCache[key]; ok && len(values) > 0 {
    		return values, true
    	}
    	return []string{}, false
    }
    
    func (c *Context) getQueryCache() {
       if c.queryCache == nil {
          c.queryCache = c.Request.URL.Query()
       }
    }
    

    其中 c.Request.URL.Query() 这个⽅法就是把 ?k=v&k1=v1&k2=v2 这类查询键值对转换为

    map[string][]string ,所以还是很耗性能的,这⾥ Gin 采⽤了缓存的做法提⾼了性能挺好,这也是 Gin 成为性能最快的Golang Web 框架的原因之⼀。

    4 接收数组和 Map

    QueryArray

    例如实际业务中,URL⼤概是这样的 ?a=b&a=c&a=d , key 值都⼀ 样,但是对应的 value 不⼀样。

    这类URL查询参数,就是⼀个数组,那么在Gin中我们如何获取它们呢?

    // 在浏览器里访问http://localhost:8080/?media=blog&media=wechat 会看到如下信息:
    // ["blog","wechat"]
    func main() {
       r := gin.Default()
       r.GET("/", func(c *gin.Context) {
          fmt.Println("media:", c.QueryArray("media"))
          c.JSON(200, c.QueryArray("media"))
       })
       r.Run(":8080")
    }
    

    QueryArray ⽅法也有对应的 GetQueryArray ⽅法,区别在于返回对应的 key 是否存在

    QueryMap

    把满⾜⼀定格式的URL查询参数,转换为⼀个 map

    例如:访问:http://localhost:8080/?ids[0]=a&ids[1]=b&ids[2]=c

    输出:{“0”:“a”,“1”:“b”,“2”:“c”}

    func main() {
    
       r := gin.Default()
    
       r.GET("/", func(c *gin.Context) {
          fmt.Println("map:", c.QueryMap("ids"))
          c.JSON(200, c.QueryMap("ids"))
       })
       r.Run(":8080")
    }
    

    其中 QueryMap 的原理和具体源码实现:

    // QueryMap returns a map for a given query key.
    func (c *Context) QueryMap(key string) map[string]string {
       dicts, _ := c.GetQueryMap(key)
       return dicts
    }
    
    // GetQueryMap returns a map for a given query key, plus a boolean value
    // whether at least one value exists for the given key.
    func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
       c.getQueryCache()
       return c.get(c.queryCache, key)
    }
    
    // get is an internal method and returns a map which satisfy conditions.
    func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
    	dicts := make(map[string]string)
    	exist := false
    	for k, v := range m {
    		if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
    			if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
    				exist = true
    				dicts[k[i+1:][:j]] = v[0]
    			}
    		}
    	}
    	return dicts, exist
    }
    

    5 Form 表单

    待补充

    6 上传⽂件

    上传单个文件 FormFile

    test目录下的html文件源码:

    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
        <form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">
            头像:
            <input type="file" name="file">
            <br>
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    
    func main() {
       // 1创建路由,默认使用了两个中间件Logger(),Recovery()
       r := gin.Default()
       // 给表单限制上传大小 (默认 32 MiB)
       r.MaxMultipartMemory = 8 << 20 // 8 MiB
       r.Static("/", "./test")
       // 2绑定路由规则,
       // gin.Context,封装了request和respose
       r.POST("/upload", func(c *gin.Context) {
          
    
          file, _ := c.FormFile("file")
          log.Println("file:", file.Filename)
          c.SaveUploadedFile(file, "./"+"test/"+file.Filename) // 上传文件到指定的路径
          c.String(200, fmt.Sprintf("%s upload file!", file.Filename))
       })
       // 3监听端口,默认8080
       r.Run(":8080")
    }
    

    上传多个文件,就是在上传单个文件的基础上 循环遍历文件列表而已

    public 下的html文件为

    
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Multiple file upload</title>
    </head>
    <body>
    <h1>Upload multiple files with fields</h1>
    
    <form action="/upload" method="post" enctype="multipart/form-data">
        Name: <input type="text" name="name"><br>
        Email: <input type="email" name="email"><br>
        Files: <input type="file" name="files" multiple><br><br>
        <input type="submit" value="Submit">
    </form>
    </body>
    </html>
    
    func main() {
       router := gin.Default()
       // Set a lower memory limit for multipart forms (default is 32 MiB)
       router.MaxMultipartMemory = 8 << 20 // 8 MiB
       router.Static("/", "./public")
       router.POST("/upload", func(c *gin.Context) {
    
          name := c.PostForm("name")
          email := c.PostForm("email")
    
          // Multipart form
          form, err := c.MultipartForm()
          if err != nil {
             c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
             return
          }
          files := form.File["files"]
    
          for _, file := range files {
             log.Println("file:", file.Filename)
             filename := filepath.Base(file.Filename)
             if err := c.SaveUploadedFile(file, filename); err != nil {
                c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
                return
             }
          }
    
          c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email))
       })
       router.Run(":8080")
    }
    

    7 分组路由

    ⽐如基于模块化,把同样模块的放在⼀起,⽐如 基于版本,把相同版本的API放⼀起,便于使⽤。在有的框架中,分组路由也被称之为命名空间

    url分组,可以是分版本 等等

    func main() {
    	r := gin.Default()
    	//路由组注册中间件方法1:
    	xx1Group := r.Group("/xx1", func(c *gin.Context) { fmt.Println("/xx1中间件") })
    	{
    		xx1Group.GET("/index", func(c *gin.Context) {
    			c.JSON(http.StatusOK, gin.H{"msg": "xx1Group"})
    		})
    		xx1Group.GET("/index2", func(c *gin.Context) {
    			c.JSON(http.StatusOK, gin.H{"msg": "2222xx1Group"})
    		})
    	}
    	//路由组注册中间件方法2:
    	xx2Group := r.Group("/xx2")
    	xx2Group.Use(authMiddleware(true))
    	{
    		xx2Group.GET("/index", func(c *gin.Context) {
    			c.JSON(http.StatusOK, gin.H{"msg": "xx2Group"})
    		})
    	}
    	r.Run(":8080")
    }
    
    

    路由中间件

    通过 Group ⽅法的定义,我们可以看到,它是可以接收两个参数的:

    func (group *RouterGroup) Group(relativePath string, handlers …HandlerFunc) *RouterGroup

    第⼀个就是我们注册的分组路由(命名空间);第⼆个是⼀个 …HandlerFunc ,可以把它理解为这个 分组路由的中间件,所以这个分组路由下的⼦路由在执⾏的时候,都会调⽤它

    如上述代码,访问xx1/index2 或者 xx1/index 都会打印出 /xx1中间件

    分组路由嵌套

    和上述分组的做法是一致

    原理解析

    以get为例

    注意第⼀个参数 relativePath ,这是⼀个相对路径,也就是我们传给 Gin 的是⼀个相对路径,那么是 相对谁的呢?

    func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
       return group.handle(http.MethodGet, relativePath, handlers)
    }
    

    通过这句 absolutePath := group.calculateAbsolutePath(relativePath) 代码,我们可以 看出是相对当前的这个 group (⽅法接收者)的。 现在 calculateAbsolutePath ⽅法的源代码我们暂时不看,回过头来看 Group 这个⽣成分组路由的 ⽅法。

    func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
       absolutePath := group.calculateAbsolutePath(relativePath)
       handlers = group.combineHandlers(handlers)
       group.engine.addRoute(httpMethod, absolutePath, handlers)
       return group.returnObj()
    }
    
    func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
       return &RouterGroup{
          Handlers: group.combineHandlers(handlers),
          basePath: group.calculateAbsolutePath(relativePath),
          engine:   group.engine,
       }
    }
    

    这⾥要注意的是,我们通过 gin.Default() ⽣成的 gin.Engine 其实包含⼀个 RouterGroup (嵌套组 合),所以它可以⽤ RouterGroup 的⽅法。 Group ⽅法⼜⽣成了⼀个 *RouterGroup ,这⾥最重要的就是 basePath ,它的值是 group.calculateAbsolutePath(relativePath) ,和我们刚刚暂停的分析的⽅法⼀样,既然这 样,就来看看这个⽅法吧。

    func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
       return joinPaths(group.basePath, relativePath)
    }
    

    GIn中间件

    Gin框架允许开发者在处理请求的过程中,加⼊⽤户⾃⼰的钩⼦(Hook)函数。这个钩⼦函数就叫中间件,中间件适合处理⼀些公共的业务逻辑,⽐如登录认证、权限校验、数据分⻚、记录⽇志、耗时统计等

    在Gin中,我们可以通过Gin提供的默认函数,来构建⼀个⾃带默认中间件的 *Engine 。

     r := gin.Default()
    

    Default 函数会默认绑定两个已经准备好的中间件,它们就是Logger 和 Recovery,帮助我们打印⽇志 输出和 painc 处理。

    func Default() *Engine {
    	debugPrintWARNINGDefault()
    	engine := New()
    	engine.Use(Logger(), Recovery())
    	return engine
    }
    

    从中我们可以看到,Gin的中间件是通过 Use ⽅法设置的,它接收⼀个可变参数,所以我们同时可以设置 多个中间件。

    func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
       engine.RouterGroup.Use(middleware...)
       engine.rebuild404Handlers()
       engine.rebuild405Handlers()
       return engine
    }
    

    其实就是Gin定义的⼀个 HandlerFunc ,⽽它在我 们Gin中经常使⽤

    r.GET("/", func(c *gin.Context) {
    	fmt.Println("HandlerFunc") 
    	c.JSON(200, "HandlerFunc")
    	})
    

    后⾯的 func(c *gin.Context) 这部分其实就是⼀个 HandlerFunc

    中间件实现HTTP Basic Authorization

    HTTP Basic Authorization 是HTTP常⽤的认证⽅案,它通过Authorization 请求消息头含有服务器⽤于 验证⽤户代理身份的凭证,格式为:

    Authorization: Basic <credentials>

    如果认证不成功,服务器返回401 Unauthorized 状态码以及WWW-Authenticate 消息头,让客户端输⼊

    ⽤户名和密码进⼀步认证。

    在Gin中,为我们提供了 gin.BasicAuth 帮我们⽣成基本认证的中间件,⽅便我们的开发。

    基本认证的中间件可以用在分组路由中,在特定的url下进行认证

    func main() {
       r := gin.Default()
       r.Use(gin.BasicAuth(gin.Accounts{
          "admin": "123456",
       }))
       
    
       r.GET("/", func(c *gin.Context) {
          body, _ := ioutil.ReadAll(c.Request.Body)
          fmt.Println("---body--- \r\n " + string(body))
          fmt.Println("---header--- \r\n")
          for k, v := range c.Request.Header {
             fmt.Println(k, v)
          }
          fmt.Println("进入主页")
          c.JSON(200, "首页")
       })
    
       r.Run(":8080")
    }
    

    中间件注意事项

    gin.Default()

    gin.Default()默认使⽤了Logger和Recovery中间件,其中:Logger中间件将⽇志写⼊ gin.DefaultWriter,即使配置GIN_MODE=release。Recovery中间件会recover任何panic。如果有 panic的话,会写⼊500响应码。如果不想使⽤上⾯两个默认的中间件,可以使⽤gin.New()新建⼀个没有 任何默认中间件的路由。

    gin中间件中使⽤goroutine

    当在中间件或handler中启动新的goroutine时,不能使⽤原始的上下⽂(c *gin.Context),必须使 ⽤其只读副本(c.Copy())

    image-20210321212453452

    gin框架中间件c.Next()理解

    func main() {
       router := gin.New()
    
       mid1 := func(c *gin.Context) {
          fmt.Println("mid1 start")
          c.Next()
          fmt.Println("mid1 end")
       }
       mid2 := func(c *gin.Context) {
          fmt.Println("mid2 start")
          c.Next()
          fmt.Println("mid2 end")
       }
       mid3 := func(c *gin.Context) {
          fmt.Println("mid3 start")
          c.Next()
          fmt.Println("mid3 end")
       }
       router.Use(mid1, mid2)
       router.Use(mid3)
       router.GET("/index", func(c *gin.Context) {
          fmt.Println("process get request")
          c.JSON(http.StatusOK, "hello")
          fmt.Println("JSON after") //
          // c.Next() // 这里加是没有用
       })
    
       router.Run(":8080")
    }
    
    • 正常写next是如下打印 ,类似于递归,洋葱模型

      mid1 start
      mid2 start
      mid3 start
      process get request
      JSON after
      mid3 end
      mid2 end
      mid1 end
      
    • 如果注释掉3个中间件中的c.Next(),则执⾏情况如下,顺序调用每一个中间件

      mid1 start
      mid1 end
      mid2 start
      mid2 end
      mid3 start
      mid3 end
      process get request
      JSON after
      
    • 只在m1中写入c.Next()

      mid1 start
      mid2 start
      mid2 end
      mid3 start
      mid3 end
      process get request
      JSON after
      mid1 end
      

    总结:

    最后的get路由处理函数可以理解为最后的中间件,在不是调⽤c.Abort()的情况下,所有的中间件 都会被执⾏到。当某个中间件调⽤了c.Next(),则整个过程会产⽣嵌套关系。如果某个中间件调⽤了 c.Abort(),则此中间件结束后会直接返回,后⾯的中间件均不会调⽤

    8 json、struct、xml、yaml、protobuf渲染

    各种数据格式的响应

    func main() {
    	r := gin.Default()
    	//1. json响应
    	r.GET("/someJSON", func(c *gin.Context) {
    		c.JSON(200, gin.H{"message": "someJSON", "status": 200})
    	})
    	//2. 结构体响应
    	r.GET("/someStruct", func(c *gin.Context) {
    		var msg struct {
    			Name    string
    			Message string
    			Number  int
    		}
    		msg.Name = "root"
    		msg.Message = "message"
    		msg.Number = 123
    		c.JSON(200, msg)
    	})
    
    	//3. XML
    	r.GET("/someXML", func(c *gin.Context) {
    		c.XML(200, gin.H{"message": "abc"})
    	})
    
    	//4. YAML响应
    	r.GET("/someYAML", func(c *gin.Context) {
    		c.YAML(200, gin.H{"name": "you"})
    	})
    
    	//5.Protobuf格式,谷歌开发的高效存储读取的工具
    	r.GET("/someProtoBuf", func(c *gin.Context) {
    		reps := []int64{int64(1), int64(2)}
    		//定义数据
    		label := "label"
    		//传protobuf格式数据
    		data := &protoexample.Test{
    			Label: &label,
    			Reps:  reps,
    		}
    		c.ProtoBuf(200, data)
    	})
    
    	r.Run(":8080")
    }
    

    9 HTML模板渲染

    • gin⽀持加载HTML模板,然后根据模板参数进⾏配置并返回响应的数据,本质上就是字符串替换

    • LoadHTMLGlob()⽅法可以加载模板⽂件

    正常渲染html模板

    func main() {
       r := gin.Default()
       r.LoadHTMLGlob("view/*")
       r.GET("/index", func(c *gin.Context) {
          c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是gin", "name": "you"})
       })
       r.GET("/", func(c *gin.Context) {
          c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是gin", "name": "you"})
       })
       r.Run(":8080")
    }
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{.title}}</title>
    </head>
    <body bgcolor="#E6E600">
    <h1>{{.title}}</h1>
    name : {{.name}}
    </body>
    </html>
    

    将html文件头尾分离

    func main() {
       r := gin.Default()
       r.LoadHTMLGlob("view2/**/*")
       r.GET("/index", func(c *gin.Context) {
          c.HTML(http.StatusOK, "user/index.html", gin.H{"title": "我是gin", "name": "you2"})
       })
       r.Run()
    }
    

    index.html

    {{ define "user/index.html" }}
        {{template "public/header" .}}
        name: {{.name}}
        {{template "public/footer" .}}
    {{ end }}
    

    header.html

    {{define "public/header"}}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{.title}}</title>
    </head>
    <body>
    {{end}}
    

    footer.html

    {{define "public/footer"}}
          </body>
          </html>
      {{end}}
    

    url重定向

    访问http://127.0.0.1:8080/ 会 自动重定向到 http://127.0.0.1:8080/index

    func main() {
       r := gin.Default()
       r.LoadHTMLGlob("view/*")
       r.GET("/index", func(c *gin.Context) {
          c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是gin", "name": "you"})
       })
       r.GET("/", func(c *gin.Context) {
          c.Redirect(http.StatusMovedPermanently, "/index")  // 重定向
       })
       r.Run(":8080")
    }
    

    静态⽂件⽬录

    需要引⼊静态⽂件可以定义⼀个静态⽂件⽬录

    r.Static("/assets", "./assets")
    

    10 异步协程

    • goroutine机制可以⽅便地实现异步处理
    • 另外,在启动新的goroutine时,不应该使⽤原始上下⽂,必须使⽤它的只读副本。
    func main() {
       r := gin.Default()
       //1. 异步
       r.GET("/long_async", func(c *gin.Context) {
          //需要搞一个副本
          copyContext := c.Copy()
          //异步处理
          go func() {
             time.Sleep(3 * time.Second)
             log.Println("异步执行:" + copyContext.Request.URL.Path)
             // copyContext.JSON(200, gin.H{"message": "someJSON", "status": 200})
          }()
       })
    
       //2. 同步
       r.GET("/long_sync", func(c *gin.Context) {
          time.Sleep(3 * time.Second)
          log.Println("同步执行:" + c.Request.URL.Path)
       })
       r.Run()
    }
    

    作者:小魔童哪吒

    展开全文
  • 在 上一篇 Golang Gin 实战(二)| 简便的Restful API 实现 文章中,我们留了一个疑问,假如我们有很多用户,我们要为他们一个个注册路由(路径)吗?路由路径如下URL:/users/123 /users/456 /users/23456以上等等...

    在 上一篇 Golang Gin 实战(二)| 简便的Restful API 实现 文章中,我们留了一个疑问,假如我们有很多用户,我们要为他们一个个注册路由(路径)吗?

    路由路径

    如下URL:

    /users/123
    /users/456
    /users/23456

    以上等等,我们有很多用户,如果我们都一个个为这些用户注册这些路由(URL),那么我们是很难注册完的,而且我们还会有新注册的用户,可见这种办法不行。

    我们观察这些路由(URL),发现它们具备一定的规则:前面都是users,后面是usersid。这样我们就可以把这些路由归纳为:

    /users/id

    这样我们就知道只有id这部分是可以变的,前面的users是不变的。可变的id可以当成我们API服务输入的参数,这样我们就可以通过这个id参数,获取对应的用户信息,这种URL匹配的模式,我们称之为路由参数。

    路由参数

    Gin中,要实现以上路由参数非常简单:

    func main() {
        r := gin.Default()
    
        r.GET("/users/:id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(200, "The user id is  %s", id)
        })
        r.Run(":8080")
    }
    

    我们运行如上代码,打开浏览器,输入http://localhost:8080/users/123,就可以看到如下信息:

    The user id is  123

    我们可以更换http://localhost:8080/users/123中的id 123 为其他字符串,会发现都可以正常打印,这就是路由匹配、路由正则,或者路由参数。

    Gin的路由采用的是httprouter,所以它的路由参数的定义和httprouter也是一样的。

    /users/:id 就是一种路由匹配模式,也是一个通配符,其中:id就是一个路由参数,我们可以通过c.Param("id")获取定义的路由参数的值,然后用来做事情,比如打印出来。

    /users/:id这种匹配模式是精确匹配的,只能匹配一个,我们举几个例子说明:

    Pattern: /users/:id
    
    /users/123          匹配
    /users/哈哈        匹配
    /users/123/go      不匹配
    /users/             不匹配

    这里我故意写了/users/哈哈,并且是匹配的,意思就是对于Gin路径中的匹配都是字符串,它是不区分数字、字母和汉字的,都匹配。

    这里还需要说明的是,Gin的路由是单一的,不能有重复。比如这里我们注册了/users/:id,那么我们就不能再注册匹配/users/:id模式的路由,比如:

    r.GET("/users/list", func(c *gin.Context) {
        //省略无关代码
    })

    这时候我们运行程序的话,会出现如下提示:

    panic: 'list' in new path '/users/list' conflicts with existing wildcard ':id' in existing prefix '/users/:id'

    通配符重复了,路由必须要唯一。Gin内部使用的路由是httprouter,我这里前段时间正好有一篇关于httprouter的详细分析,可以看下。Go语言经典库使用分析(七)| 高性能可扩展 HTTP 路由 httprouter

    星号路由参数

    上面我们介绍的是:号的路由参数,这种路由参数最常用。还有一种不常用的就是*号类型的参数,表示匹配所有。

    /users/*id为例:

    Pattern: /users/*id
    
    /users/123         匹配
    /users/哈哈        匹配
    /users/123/go      匹配
    /users/            匹配

    我们把上面的例子改下:

    func main() {
        r := gin.Default()
    
        r.GET("/users/*id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(200, "The user id is  %s", id)
        })
        r.Run(":8080")
    }
    

    现在我们运行,浏览器里访问http://localhost:8080/users/123,会看到如下信息:

    The user id is  /123

    是否发现区别了,我们获取到的id不是123了,而是/123,多了一个/

    同样的你试试http://localhost:8080/users/123/go会发现显示的信息是:

    The user id is  /123/go

    是一个/开头的路径字符串。

    这里要特别说明一点的是,如果你用浏览器访问http://localhost:8080/users,会被重定向到http://localhost:8080/users/,然后显示的信息如下:

    The user id is  /

    重定向的根本原因在于/users没有匹配的路由,但是有匹配/users/的路由,所以就会被重定向到/users/。现在我们注册一个/users来验证下这个猜测:

    r.GET("/users", func(c *gin.Context) {
        c.String(200, "这是真正的/users")
    })
    

    现在再访问http://localhost:8080/users,会看到显示的信息变成了:

    这是真正的/users

    这也间接证明了/users/*id/users这两个路由是不冲突的,可以被Gin注册。

    以上自动重定向的原理,得益于gin.RedirectTrailingSlash 等于true的配置。如果我们把它改为false就不会自动重定向了。

    func main() {
        r := gin.Default()
    
        r.RedirectTrailingSlash = false
        r.GET("/users/*id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(200, "The user id is  %s", id)
        })
    
        r.Run(":8080")
    }
    

    现在我们运行程序,访问http://localhost:8080/users发现显示的信息是404 page not found

    小结

    这一篇主要介绍路由参数,并且基于这种参数,我们可以很灵活的实现我们的API,并且从路径中获取相应的参数进行操作。对于*号参数,不建议使用,因为匹配的太多,会导致我们自己搞不清楚哪些路由被注册了。

    除了路由参数,还有URL的query参数,也就是?a=b&c=d这样的格式,下一篇文章我们再介绍。

    精彩文章推荐

    Golang Gin 实战(一)| 快速安装入门

    Golang Gin 实战(二)| 简便的Restful API 实现

    Go语言经典库使用分析(三)| Gorilla Handlers 详细介绍

    为了答谢新老朋友的转发、阅读和点赞支持,我给大家包了个现金红包,关注我的公众号,即可参与抽奖。在看到50,下次抽奖增加金额!

    我有几个的Go语言交流微信群,可以扫码关注公众号flysnow_org或者者网站 https://www.flysnow.org/,加我好友,我拉你进来。

    fdc3564b2dc7dbfc64d0bdce77e7f84c.png
    展开全文
  • 背景如果我们之前搞过java web开发,我们应该都知道swagger这个API文档自动生成利器,有了swagger可以...快速整合一下gin和swagger,我们将在上一个分层的gin实战基础上,我们开始整合swaggerswaggerswagger的github...

    背景

    如果我们之前搞过java web开发,我们应该都知道swagger这个API文档自动生成利器,有了swagger可以方便我们与客户端的联调,基本上是一目了然,swagger支持java,自然它也能支持golang的gin框架,本小节,就花费5分钟的时间,快速整合一下gin和swagger,我们将在上一个分层的gin实战基础上,我们开始整合swagger

    bf30a64e46a4c099d0eadcc8160feb1c.png

    swagger

    swagger的github地址

    https://github.com/swaggo/gin-swagger

    Step1

    打开我们上一个小节搭建的工程,整体结构图如下图所示

    383f9f5a90dc7fb9540dee2bf30143ac.png

    项目骨架图

    我们打开goland的一个终端,运行如下命令

    go get -u github.com/swaggo/swag/cmd/swag

    6cbdf349b9416a5822197508e57e4832.png

    在沒有报错的情况下,再运行swag init

    a7a6f76ad04708362c3895a38408629b.png

    这个时候你会发现你的项目中多了一个文件夹

    87c94079f9c092d889fbd3bc1af4864f.png

    Step2

    在完成Step1之后,swagger的依赖基本上我们都有了,接下来,我们就要gin对swagger的url映射,或者我们在访问浏览器的时候,我们怎么知道对应请求Swagger页面的URL呢,我们回到我们之前配置映射的地方,如下图所示,我们一开始只有一个对/movie/get/:id的依赖,接下来我们要新增对swagger的映射

    350c6104937f3aa0e1077b800371b6d8.png

    修改一下,代码编程如下,这个一般是固定格式,但是我们也要稍微了解一下其中的原理,其实并不复杂,如下代码所示,下面第二处标红的表示,我们多加一个GET映射,以/swagger前缀开头的都交给你ginSwagger.WrapHandler这个处理器处理

    1f68ba287421accf401a1c56d9142406.png

    好了,到此我们重新运行一下swag init,然后打开浏览器访问如下地址

    http://localhost:8080/swagger/index.html

    34f445b49f9f73c9bf293d24e00611df.png

    到此为止,我们已经初步看到了swagger的熟悉的页面了,虽然有报错,但这是我们前进的一大步,接下来我们一步步进行润色就可以了

    Step3

    我们为我们的swagger新增标题和说明,在golang中,我们只需要写一些注解就可以达到上述的效果,我们在main函数中,增加如下的注释和引入一下swagger doc的依赖,如下所示,重新运行swag init

    baa9df20dbf4b47e4ce66913562036c9.png

    重启项目,再次打开浏览器,再次访问上述网址,我们发现项目不再报错,也可以看到该文档的一些基本信息

    e554a68554035ec9b080c1db11ab82ea.png

    不报错的swagger页面

    Step4

    为/movie/get/:id 新增接口说明,在spring mvc中,我们可以在接口方法上加一些注解,然后在入参和出参上加一些特定注解,说明参数含义,然后swagger就会自动帮助我们变成接口说明,在golang中也是一样,我们再handlers中的各个func上加注释,而不是加注解,也可以完成一样的工作,我们在GetMovieById上加上如下的注释,然后再次运行swag init就可以完成第一个接口说明了

    8be549597122211e716e683f03fdbf8f.png

    重启系统,然后再次访问浏览器,我们可以看到最后的成果就出来了

    8ea6235e76b676702eee1f6179c8c2e4.png

    并且我们可以成功的在里面请求我们的系统接口了

    93c8fad56f614a902c0d579526a1ee3d.png

    小结

    简而言之,golang版的swagger还是比较易懂好用,并且容易上手的,但是我们每次修改完注释,都要重新swag init重新生成最新的文档说明,这个也是我们要做注意的,下一个小节,我们将完整的编写关于电影数据库的增删改查的功能

    展开全文
  • Golang Gin 实战(三)| 路由参数

    千次阅读 2019-12-13 18:30:00
    在 上一篇Golang Gin 实战(二)| 简便的Restful API 实现文章中,我们留了一个疑问,假如我们有很多用户,我们要为他们一个个注册路由(路径)吗? 路由路径 如下URL: /users/123 /users/456 /users/23456 ...

    在 上一篇 Golang Gin 实战(二)| 简便的Restful API 实现 文章中,我们留了一个疑问,假如我们有很多用户,我们要为他们一个个注册路由(路径)吗?

    路由路径

    如下URL:

    /users/123
    /users/456
    /users/23456
    

    以上等等,我们有很多用户,如果我们都一个个为这些用户注册这些路由(URL),那么我们是很难注册完的,而且我们还会有新注册的用户,可见这种办法不行。

    我们观察这些路由(URL),发现它们具备一定的规则:前面都是users,后面是usersid。这样我们就可以把这些路由归纳为:

    /users/id
    

    这样我们就知道只有id这部分是可以变的,前面的users是不变的。可变的id可以当成我们API服务输入的参数,这样我们就可以通过这个id参数,获取对应的用户信息,这种URL匹配的模式,我们称之为路由参数。

    路由参数

    Gin中,要实现以上路由参数非常简单:

    func main() {
        r := gin.Default()
    
        r.GET("/users/:id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(200, "The user id is  %s", id)
        })
        r.Run(":8080")
    }
    

    我们运行如上代码,打开浏览器,输入http://localhost:8080/users/123,就可以看到如下信息:

    The user id is  123
    

    我们可以更换http://localhost:8080/users/123中的id 123 为其他字符串,会发现都可以正常打印,这就是路由匹配、路由正则,或者路由参数。

    Gin的路由采用的是httprouter,所以它的路由参数的定义和httprouter也是一样的。

    /users/:id 就是一种路由匹配模式,也是一个通配符,其中:id就是一个路由参数,我们可以通过c.Param("id")获取定义的路由参数的值,然后用来做事情,比如打印出来。

    /users/:id这种匹配模式是精确匹配的,只能匹配一个,我们举几个例子说明:

    Pattern: /users/:id
    
    /users/123          匹配
    /users/哈哈        匹配
    /users/123/go      不匹配
    /users/             不匹配
    

    这里我故意写了/users/哈哈,并且是匹配的,意思就是对于Gin路径中的匹配都是字符串,它是不区分数字、字母和汉字的,都匹配。

    这里还需要说明的是,Gin的路由是单一的,不能有重复。比如这里我们注册了/users/:id,那么我们就不能再注册匹配/users/:id模式的路由,比如:

    r.GET("/users/list", func(c *gin.Context) {
        //省略无关代码
    })
    

    这时候我们运行程序的话,会出现如下提示:

    panic: 'list' in new path '/users/list' conflicts with existing wildcard ':id' in existing prefix '/users/:id'
    

    通配符重复了,路由必须要唯一。Gin内部使用的路由是httprouter,我这里前段时间正好有一篇关于httprouter的详细分析,可以看下。Go语言经典库使用分析(七)| 高性能可扩展HTTP路由httprouter

    星号路由参数

    上面我们介绍的是:号的路由参数,这种路由参数最常用。还有一种不常用的就是*号类型的参数,表示匹配所有。

    /users/*id为例:

    Pattern: /users/*id
    
    /users/123         匹配
    /users/哈哈        匹配
    /users/123/go      匹配
    /users/            匹配
    

    我们把上面的例子改下:

    func main() {
        r := gin.Default()
    
        r.GET("/users/*id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(200, "The user id is  %s", id)
        })
        r.Run(":8080")
    }
    

    现在我们运行,浏览器里访问http://localhost:8080/users/123,会看到如下信息:

    The user id is  /123
    

    是否发现区别了,我们获取到的id不是123了,而是/123,多了一个/

    同样的你试试http://localhost:8080/users/123/go会发现显示的信息是:

    The user id is  /123/go
    

    是一个/开头的路径字符串。

    这里要特别说明一点的是,如果你用浏览器访问http://localhost:8080/users,会被重定向到http://localhost:8080/users/,然后显示的信息如下:

    The user id is  /
    

    重定向的根本原因在于/users没有匹配的路由,但是有匹配/users/的路由,所以就会被重定向到/users/。现在我们注册一个/users来验证下这个猜测:

    r.GET("/users", func(c *gin.Context) {
        c.String(200, "这是真正的/users")
    })
    

    现在再访问http://localhost:8080/users,会看到显示的信息变成了:

    这是真正的/users
    

    这也间接证明了/users/*id/users这两个路由是不冲突的,可以被Gin注册。

    以上自动重定向的原理,得益于gin.RedirectTrailingSlash 等于true的配置。如果我们把它改为false就不会自动重定向了。

    func main() {
        r := gin.Default()
    
        r.RedirectTrailingSlash = false
        r.GET("/users/*id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(200, "The user id is  %s", id)
        })
    
        r.Run(":8080")
    }
    

    现在我们运行程序,访问http://localhost:8080/users发现显示的信息是404 page not found

    小结

    这一篇主要介绍路由参数,并且基于这种参数,我们可以很灵活的实现我们的API,并且从路径中获取相应的参数进行操作。对于*号参数,不建议使用,因为匹配的太多,会导致我们自己搞不清楚哪些路由被注册了。

    除了路由参数,还有URL的query参数,也就是?a=b&c=d这样的格式,下一篇文章我们再介绍。

    感谢新老朋友的转发、阅读和点赞支持,给大家抽个现金红包(点击参与),在看到50,下次抽奖增加金额!

    我有上千人的Go语言交流群,可以扫码关注公众号flysnow_org或者网站 https://www.flysnow.org/,加我微信好友,我拉你进来。

    往期精彩回顾

    Golang Gin 实战(二)| 简便的Restful API 实现

    Golang Gin 实战(一)| 快速安装入门


    Go语言经典库使用分析(七)| 高性能可扩展HTTP路由httprouter

    扫码关注

    展开全文
  • Golang Gin 实战(五)| 接收数组和 Map

    千次阅读 2019-12-19 18:28:00
    在 上一篇Golang Gin 实战(四)| URL查询参数的获取和原理分析文章中,因为文章篇幅问题,QueryArray和QueryMap没有介绍,这篇文章继续。 QueryArray 在实际的业务开发中,我们有些业务多选的,比如一个活动有多...
  • Golang Gin 实战(一)| 快速安装入门Gin 是一个非常优秀的Golang Web Framework,它不光API友好,性能也非常高,并且设计简洁,便于入门。所以它(Gin)非常受欢迎,在Github上已经三万三千多个星星,也是我最喜欢的...
  • Golang Gin 实战(一)| 快速安装入门Gin 是一个非常优秀的Golang Web Framework,它不光API友好,性能也非常高,并且设计简洁,便于入门。所以它(Gin)非常受欢迎,在Github上已经三万三千多个星星,也是我最喜欢的...
  • 在 上一篇 Golang Gin 实战(二)| 简便的Restful API 实现 文章中,我们留了一个疑问,假如我们有很多用户,我们要为他们一个个注册路由(路径)吗?路由路径如下URL:/users/123/users/456/users/23456以上等等,我们...
  • Golang Gin 实战(十二)| ProtoBufProtoBuf最近几年也算比较流行,它是一种语言无关,平台无关,并且可以扩展,并结构数据序列化的方法。相比JSON/XML这类文本...
  • 我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,...
  • 在 上一篇 Golang Gin 实战(四)| URL查询参数的获取和原理分析 文章中,因为文章篇幅问题,QueryArray和QueryMap没有介绍,这篇文章继续。QueryArray在实际的业务开发中,我们有些业务多选的,比如一个活动有多个...
  • Gin 实战学习笔记

    2019-08-28 16:54:04
    authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ “foo”: “bar”, “austin”: “1234”, “lena”: “hello2”, “manu”: “4321”, })) 网页访问时会弹出输入窗口,curl使用则是在...
  • 在 上一篇Golang Gin 实战(三)| 路由参数文章中,主要介绍了路由通配符、路由参数,让我们有了一种可以从URL路径中获取参数的方式,同时又不是重复的注册相似的路由。 这一篇,主要介绍查询参数,以及获取查询...
  • 在 上一篇 Golang Gin 实战(四)| URL查询参数的获取和原理分析 文章中,因为文章篇幅问题,QueryArray和QueryMap没有介绍,这篇文章继续。QueryArray在实际的业务开发中,我们有些业务多选的,比如一个活动有多个人...
  • 在 上一篇Golang Gin 实战(四)| URL查询参数的获取和原理分析文章中,因为文章篇幅问题,QueryArray和QueryMap没有介绍,这篇文章继续。QueryArray在实际的业务开发中,我们有些业务多选的,比如一个活动有多个人...
  • 在 上一篇 Golang Gin 实战(四)| URL查询参数的获取和原理分析 文章中,因为文章篇幅问题,QueryArray和QueryMap没有介绍,这篇文章继续。QueryArray在实际的业务开发中,我们有些业务多选的,比如一个活动有多个...
  • Gin的整个实现中,中间件可谓是Gin的精髓。一个个中间件组成一条中间件链,对HTTP Request请求进行拦截处理,实现了代码的解耦和分离,并且中间件之间相互不用感知到,每个中间件只需要处理自己需要处理的事情即可...
  • 在 上一篇Golang Gin 实战(三)| 路由参数文章中,主要介绍了路由通配符、路由参数,让我们有了一种可以从URL路径中获取参数的方式,同时又不是重复的注册相似的路由。这一篇,主要介绍查询参数,以及获取查询参数的...
  • 我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,...
  • 在 上一篇Golang Gin 实战(三)| 路由参数文章中,主要介绍了路由通配符、路由参数,让我们有了一种可以从URL路径中获取参数的方式,同时又不是重复的注册相似的路由。这一篇,主要介绍查询参数,以及获取查询参数的...
  • 我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,...
  • 我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,...
  • 我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,...
  • Gin 是一个非常优秀的Golang Web Framework,它不光API友好,性能也非常高,并且设计简洁,便于入门。所以它(Gin)非常受欢迎,在Github上已经三万三千多个星星,也是我最喜欢的Web 框架。入门要求要想使用Golang Gin...

空空如也

空空如也

1 2 3 4 5 ... 15
收藏数 285
精华内容 114
关键字:

gin实战