go web框架gin介绍和使用(一)

原创
2019/10/12 17:56
阅读数 1.7K

本文是gin介绍和使用的第一篇文章。

一、简要介绍

Gin的官方说明如下:

Gin is a web framework written in Go (Golang). It features a martini-like API with much better
performance, up to 40 times faster thanks to httprouter. If you need performance and good
productivity, you will love Gin.

官方地址:https://github.com/gin-gonic/gin

简单来说,Gin是一个Go的微框架,基于 httprouter,提供了类似martini(也是一个go web框架)但更好性能的API服务。

Gin具有运行速度快、良好的分组路由、合理的异常捕获和错误处理、优良的中间件支持和 json处理支持等优点,是一款非常值得推荐的web框架。

借助框架开发,我们不仅可以省去很多常用的封装带来的时间,也有助于良好的团队编码风格的和编码规范的养成。

二、使用详解

1.安装

  • 要安装Gin包,首先需要安装Go((Go版本1.10+)并设置Go工作区
  • 下载并安装:
go get -u github.com/gin-gonic/gin
  • 在代码中导入它
import "github.com/gin-gonic/gin"

2. 简单示例:

运行下面的代码并在浏览器中访问 http://localhost:8080/ping

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

简单几行代码,就实现了一个web服务。

首先,使用gin的Default方法创建一个路由handle;

然后,通过HTTP方法绑定路由规则和路由函数;

最后,启动路由的Run方法监听端口。

不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。

3. 常用请求方法

除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法:

func main() {
	// Creates a gin router with default middleware:
	// logger and recovery (crash-free) middleware
	router := gin.Default()

	router.GET("/someGet", getting)
	router.POST("/somePost", posting)
	router.PUT("/somePut", putting)
	router.DELETE("/someDelete", deleting)
	router.PATCH("/somePatch", patching)
	router.HEAD("/someHead", head)
	router.OPTIONS("/someOptions", options)

	// By default it serves on :8080 unless a
	// PORT environment variable was defined.
	router.Run()
	// router.Run(":3000") for a hard coded port
}

4. 获取路径中的参数

gin的路由来自httprouter库。因此httprouter具有的功能,gin也具有,不过gin不支持路由正则表达式:

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// This handler will match /user/john but will not match /user/ or /user
	router.GET("/user/:name", func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})

	// Howerer, this one will match /user/john/ and also /user/john/send
	// if no other routers match /user/john, it will redirect to /user/john/
	router.GET("/user/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := fmt.Sprintf("%s is %s", name, action)
		c.String(http.StatusOK, message)
	})

	// For each matched request Context will hold the route definition
	router.POST("/user/:name/*action", func(c *gin.Context) {
		c.String(http.StatusOK, c.Request.URL.String())
	})

	router.Run(":8000")
}

  1. 冒号加上一个参数名(如上面的":name")组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string;
  2. *号加上一个参数名(如上面的"action")组成路由参数。读取方式与冒号相同,但是号能匹配的规则更多。

使用curl(也可以使用如Postman等)访问服务,并得到测试结果如下:

5. 获取请求参数

web提供的服务通常是client和server的交互。

其中客户端向服务器发送请求,除了路由参数,其他的参数无非两种,即查询字符串query string和报文体body参数。

query string,即路由用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码的。

5.1. 获取Get请求参数

  • 客户端发起Get请求即使用query string参数,例如:
curl http://127.0.0.1:8000/welcome\?firstname\=中国\&lastname\=南京

之所以使用中文,是为了说明urlencode。

  • 服务端对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值,gin也考虑了,并且给出了一个优雅的方案:
    1. 使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值;
    2. 使用c.Query方法读取正常参数,当参数不存在的时候,返回空字串。

注意:当上述请求中的firstname为空字串的时候,服务端并不会使用默认的Guest值,空值也是值,DefaultQuery只作用于key不存在的时候,提供默认值。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// Query string parameters are parsed using the existing underlying request object.
	// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
	router.GET("/welcome", func(c *gin.Context) {
		firstname := c.DefaultQuery("firstname", "Guest")
		lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")

		c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
	})
	router.Run(":8000")
}

5.2. 获取Post请求参数

POST请求会向服务器提交数据,如提交表单数据,这就需要用到报文体body参数。 http使用报文体传输数据就比query string稍微复杂一点,常见的格式就有四种:

  • application/json(json格式数据)
  • application/x-www-form-urlencoded(就是把query string的内容,放到了body体里)
  • application/xml (xml格式数据)
  • multipart/form-data (图片上传数据)

默认情况下,c.PostFrom解析的是x-www-form-urlencoded或from-data的参数:

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	router.POST("/form_post", func(c *gin.Context) {
		message := c.PostForm("message")
		nick := c.DefaultPostForm("nick", "anonymous")
		c.JSON(http.StatusOK, gin.H{
			"status":  "posted",
			"message": message,
			"nick":    nick,
		})
	})
	router.Run(":8000")
}

与get处理query参数一样,post也提供了处理默认参数的方法c.DefaultPostForm。同理,对于c.PostForm,如果参数不存在,将会得到空字串。

前面我们使用c.String返回响应,顾名思义则返回string类型数据,content-type是plain或者text。

这里我们调用c.JSON则返回json类型数据。其中gin.H封装了生成json的方式,是一个强大的工具。使用Go可以像动态语言一样写字面量的json,对于嵌套json的实现,嵌套gin.H即可。

5.3. Get参数和Post参数混合使用

发送数据给服务端,并不是post方法才行,put方法一样也可以。同时querystring和body也不是分开的,两个同时发送也可以:

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	router.PUT("/put", func(c *gin.Context) {
		id := c.Query("id")
		page := c.DefaultQuery("page", "0")
		name := c.PostForm("name")
		message := c.PostForm("message")
		fmt.Printf("id: %s; page: %s; name: %s; message: %s\n", id, page, name, message)
		c.JSON(http.StatusOK, gin.H{
			"status": "puted",
			"info": gin.H{
				"id":      id,
				"page":    page,
				"name":    name,
				"message": message,
			},
		})
	})

	router.Run(":8000")
}

上面的例子,展示了同时使用查询字串和body参数发送数据给服务器。

6. 文件上传

前面介绍了向服务器发送请求的基本方法,其中multipart/form-data专用于文件上传。Gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。

上传文件的文件名可以由用户自定义,所以可能包含非法字符串,为了安全起见,应该由服务端统一文件名规则

6.1. 单文件上传

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// Set a lower memory limit for multipart forms (default is 32 MiB)
	// router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// single file
		name := c.DefaultPostForm("name", "template")
		fmt.Println("name:", name)
		file, header, err := c.Request.FormFile("file")
		if err != nil {
			c.String(http.StatusBadRequest, "bad request: %s", err.Error())
			return
		}
		filename := header.Filename
		out, err := os.Create(filename)
		if err != nil {
			c.String(http.StatusNotFound, "file create err: %s", err.Error())
			return
		}
		defer out.Close()
		_, err = io.Copy(out, file)
		if err != nil {
			c.String(http.StatusNotFound, "file copy err: %s", err.Error())
			return
		}
		c.String(http.StatusCreated, "upload successfully")
	})

	router.Run(":8000")
}

使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。

然后使用os的操作,把文件数据复制到硬盘上,最后返回给客户端上传成功的提示。

由于Go使用UTF-8编码格式,windows下上传其它编码格式文件可能会出现中文乱码问题,可通过另存为UTF-8编码格式文件解决。

6.2. 多文件上传

单个文件上传很简单,多文件上传与之类似,所谓多个文件,就是多一层遍历操作,每次遍历的操作步骤与单文件上传完全相同。

package main

import (
	"io"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// Set a lower memory limit for multipart forms (default is 32 MiB)
	// router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/multi/upload", func(c *gin.Context) {
		// multipart form
		err := c.Request.ParseMultipartForm(200000)
		if err != nil {
			c.String(http.StatusBadRequest, "request body out of memeory: %s", err.Error())
			return
		}
		form := c.Request.MultipartForm
		files := form.File["file"]
		for i := range files {
			file, err := files[i].Open()
			if err != nil {
				c.String(http.StatusBadRequest, "file open err: %s", err.Error())
				return
			}
			defer file.Close()
			out, err := os.Create(files[i].Filename)
			if err != nil {
				c.String(http.StatusNotFound, "file create err: %s", err.Error())
				return
			}
			defer out.Close()
			_, err = io.Copy(out, file)
			if err != nil {
				c.String(http.StatusNotFound, "file copy err: %s", err.Error())
				return
			}
		}
		c.String(http.StatusCreated, "upload successfully")
	})

	router.Run(":8000")
}

与单个文件上传类似,只不过使用了c.Request.MultipartForm得到文件句柄,再获取文件数据,然后遍历读写。

6.3. 表单上传

上面我们使用的都是curl或使用集成工具如Postman上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。

我们先要写一个表单页面,因此需要引入gin如何render(生成)模板。前面我们见识了c.String和c.JSON。下面就来看看c.HTML方法。

首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。

示例如下:

  1. 创建一个文件夹templates,然后再里面创建html文件upload.html:
<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="UTF-8"> 
    <title>upload</title> 
</head> 
 
<body> 
<h3>Single Upload</h3> 
<form action="/upload", method="post" enctype="multipart/form-data"> 
    <input type="text" value="hello gin" /> 
    <input type="file" name="file" /> 
    <input type="submit" value="upload" /> 
</form> 
 
<h3>Multi Upload</h3> 
<form action="/multi/upload", method="post" enctype="multipart/form-data"> 
    <input type="text" value="hello gin" /> 
    <input type="file" name="file" /> 
    <input type="file" name="file" /> 
    <input type="submit" value="upload" /> 
</form> 
 
</body> 
</html>

upload表单很简单,没有参数,两个form表单一个用于单个文件上传,一个用于多个文件上传。

在原有代码基础上添加如下代码:

router.LoadHTMLGlob("templates/*")
router.GET("/upload", func(c *gin.Context) {
c.HTML(http.StatusOK, "upload.html", gin.H{})
})

使用router.LoadHTMLGlob方法定义模板文件路径。

完整的代码如下:

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	router.POST("/upload", upload)

	// Set a lower memory limit for multipart forms (default is 32 MiB)
	// router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/multi/upload", multiUpload)
	// 此处使用相对路径,实际路径根据模板所在路径作相应调整
	router.LoadHTMLGlob("../templates/*")
	router.GET("/upload", func(c *gin.Context) {
		c.HTML(http.StatusOK, "upload.html", gin.H{})
	})

	router.Run(":8000")
}

func upload(c *gin.Context) {
	// single file
	name := c.DefaultPostForm("name", "template")
	fmt.Println("name:", name)
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.String(http.StatusBadRequest, "bad request: %s", err.Error())
		return
	}
	filename := header.Filename
	out, err := os.Create(filename)
	if err != nil {
		c.String(http.StatusNotFound, "file create err: %s", err.Error())
		return
	}
	defer out.Close()
	_, err = io.Copy(out, file)
	if err != nil {
		c.String(http.StatusNotFound, "file copy err: %s", err.Error())
		return
	}
	c.String(http.StatusCreated, "upload successfully")
}

func multiUpload(c *gin.Context) {
	// multipart form
	err := c.Request.ParseMultipartForm(200000)
	if err != nil {
		c.String(http.StatusBadRequest, "request body out of memeory: %s", err.Error())
		return
	}
	form := c.Request.MultipartForm
	files := form.File["file"]
	for i := range files {
		file, err := files[i].Open()
		if err != nil {
			c.String(http.StatusBadRequest, "file open err: %s", err.Error())
			return
		}
		defer file.Close()
		out, err := os.Create(files[i].Filename)
		if err != nil {
			c.String(http.StatusNotFound, "file create err: %s", err.Error())
			return
		}
		defer out.Close()
		_, err = io.Copy(out, file)
		if err != nil {
			c.String(http.StatusNotFound, "file copy err: %s", err.Error())
			return
		}
	}
	c.String(http.StatusCreated, "upload successfully")
}

通过浏览器访问

http://localhost:8000/upload

然后选择文件上传即可。

参考文章:Go语言Web框架--Gin介绍和使用

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部