Go web框架目前有很多,Beego,Gin,Iris,Revel等等。目前国内使用量比较多的是前两个,鉴于Beego的源码一直被人吐槽并且Beego使用起来太过于臃肿,框架本身构造的大而全,很多功能不一定会是你想要的这些原因,我们着重关注Gin框架的使用。Gin没有像Beego那样什么都做,它只专注于web请求的封装,如果你想做缓存,想连接数据库等等还需要使用别的框架或者使用原生的API。
Gin的github地址如下: Gin点我
安装Gin:
go get github.com/gin-gonic/gin
服务端构建一个http请求:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func HelloWordGet (c *gin.Context) {
c.String(http.StatusOK, "hello world get")
}
func HelloWordPost (c *gin.Context) {
c.String(http.StatusOK, "hello world post")
}
func main(){
// 注册一个默认的路由器
router := gin.Default()
// 最基本的用法
router.GET("/HelloWordGet", HelloWordGet)
router.POST("/HelloWordPost", HelloWordPost)
// 绑定端口是8080
router.Run(":8080")
}
启动main函数,你可以在浏览器中调用链接,当然也可以使用http包去发送请求:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, _ := http.Get("http://localhost:8080/HelloWordGet")
bytes, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("get request: %s \n",string(bytes))
response, _ := http.Post("http://localhost:8080/HelloWordPost",
"application/x-www-form-urlencode", nil)
bytes1, _ := ioutil.ReadAll(response.Body)
fmt.Printf("get request: %s \n",string(bytes1))
}
如果想模仿restful api 的方式在 路由上面带参数应该如何实现呢。Gin提供了两种匹配方式:
一种是在路由上使用:paramName
;
一种是在路由上使用:paramName/*action
,paramName后面如果跟有任何路由,只要前缀一样都会到该route上。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func HelloWordGet (c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "hello world get %s",name)
}
func HelloWordGet1 (c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "hello world get %s",name)
}
func main(){
router := gin.Default()
router.GET("/hello/get/:name", HelloWordGet)
router.GET("/hello/get/:name/*action", HelloWordGet1)
router.Run(":8080")
}
Param()
方法用于接收来自于路由上的参数。
测试:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func main() {
resp, _ := http.Get("http://localhost:8080/hello/get/xiaoming")
bytes, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("get request: %s \n",string(bytes))
}
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type User struct {
Name string
Age int
sex byte
}
func HelloWordGet1 (c *gin.Context) {
var user User
c.Bind(&user)
c.String(http.StatusOK, "hello world get %s,%s",user.Name,user.Age)
}
func HelloWordGet (c *gin.Context) {
name := c.DefaultQuery("name","NULL")
age := c.Query("age")
c.String(http.StatusOK, "hello world get %s,%s",name,age)
}
func HelloWordPost (c *gin.Context) {
name := c.PostForm("name")
sex := c.DefaultPostForm("sex", "1")
c.JSON(http.StatusOK,gin.H{"status":0,"data":name+":"+sex,"message":"success"})
}
func main(){
router := gin.Default()
router.GET("/hello/get/", HelloWordGet)
router.GET("/hello/get1/", HelloWordGe1t)
router.POST("/hello/post", HelloWordPost)
router.Run(":8080")
}
Get,Post请求方式接收参数使用的方法为Query或者DefaultQuery(),区别就是一个可以设置默认值一个不可以。
注意到在Post方法中使用了JSON()方法来返回json格式的对象。
测试:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func main() {
resp, _ := http.Get("http://localhost:8080/hello/get?name=xiohong&age=1")
bytes, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("get request: %s \n",string(bytes))
body := "{\"name\":\"xiaoming\",\"age\":12,\"sex\":1}"
response, _ := http.Post("http://localhost:8080/hello/post",
"application/x-www-form-urlencode", strings.NewReader(body))
bytes1, _ := ioutil.ReadAll(response.Body)
fmt.Printf("get request: %s \n",string(bytes1))
}
使用c.Request.FormFile(param)
方法来获取文件。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"log"
"net/http"
"os"
)
//接收客户端上传的文件然后写入本地
func UploadFile(c *gin.Context) {
file, header , err := c.Request.FormFile("file")
filename := header.Filename
fmt.Println(header.Filename)
// 创建临时接收文件
out, err := os.Create("copy_"+filename)
if err != nil {
log.Fatal(err)
}
defer out.Close()
// Copy数据
_, err = io.Copy(out, file)
if err != nil {
log.Fatal(err)
}
c.JSON(http.StatusOK,gin.H{"status":0,"data":nil,"message":"success"})
}
func main(){
router := gin.Default()
router.POST("/hello/upload", UploadFile)
router.Run(":8080")
}
测试:
构造Post请求的时候,文件要使用multipart
去承载,可以用它去设置header格式,设置文件名等等。
package main
import (
bytes3 "bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"strings"
)
func main() {
buf := new(bytes3.Buffer)
w := multipart.NewWriter(buf)
fw,_ := w.CreateFormFile("file", "1.png") //这里的uploadFile必须和服务器端的FormFile-name一致
fd,_ := os.Open("c:/1.png")
defer fd.Close()
io.Copy(fw, fd)
w.Close()
resp1,_ := http.Post("http://localhost:8080/hello/upload", w.FormDataContentType(), buf)
bytes2, _ := ioutil.ReadAll(resp1.Body)
fmt.Printf("get request: %s \n",string(bytes2))
}
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"log"
"net/http"
"os"
)
//多文件上传,接收多文件,然后写入本地
func UploadMultFile(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
for _,file := range files {
c.SaveUploadedFile(file,"c:/copy_"+file.Filename)
}
c.JSON(http.StatusOK,gin.H{"status":0,"data":nil,"message":"success"})
}
func main(){
router := gin.Default()
router.POST("/hello/multiUpload", UploadMultFile)
router.Run(":8080")
}
测试:
package main
import (
bytes3 "bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
)
func main() {
buf := new(bytes3.Buffer)
w := multipart.NewWriter(buf)
fw,_ := w.CreateFormFile("file", "1.png")
fd,_ := os.Open("c:/1.png")
defer fd.Close()
io.Copy(fw, fd)
fw1,_ := w.CreateFormFile("file", "2.png")
fd1,_ := os.Open("c:/2.png")
defer fd1.Close()
io.Copy(fw1, fd1)
w.Close()
resp1,_ := http.Post("http://localhost:8080/hello/multiUpload", w.FormDataContentType(), buf)
bytes2, _ := ioutil.ReadAll(resp1.Body)
fmt.Printf("get request: %s \n",string(bytes2))
}
上面的示例中我们每构建一个Get或者Post请求,都要将完整的路由路径写入方法参数中。Java的同学可能就会抱怨了,为啥不能像SpringMVC中的Controller一样,定义一个统一的Controller mapping 前缀,然后整个Controller中的路由都会带上该前缀呢。别急,Gin也会考虑到这种使用方式,所以提供了路由分组的功能。
func main(){
router := gin.Default()
helloGroup := router.Group("/hello")
helloGroup.GET("/get", HelloWordGet)
helloGroup.POST("/post", HelloWordPost)
helloGroup.POST("/upload", UploadFile)
helloGroup.POST("/multiUpload", UploadMultFile)
userGroup := router.Group("/user")
userGroup.GET("/get", HelloWordGet)
userGroup.POST("/post", HelloWordPost)
userGroup.POST("/upload", UploadFile)
router.Run(":8080")
}
Group(param)
方法可以上我们设置一个组名,使用该组对象发出的请求自动会带上该组名作为前缀。
可以将你本地的某些目录作为静态文件存储的目录对外提供访问。
func main(){
router := gin.Default()
//当前项目的根目录
router.StaticFS("/all",http.Dir("."))
//指定目录
router.Static("/files","c:/bin")
//指定文件
router.StaticFile("specialFile","./a/1.png")
router.Run(":8080")
}
第一个参数为url,第二个参数是要展示的路径。
Java中我们可以使用thymeleaf或者freemarker来加载html模板,Gin中提供了这个功能,同时也定义了一套自己的模板解析方法。
使用LoadHTMLGlob()
或者 LoadHTMLFiles()
:
func main(){
router := gin.Default()
router.LoadHTMLGlob("templates/*")
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "我就是个标题",
"content": "我想说一句话",
})
})
router.Run(":8080")
}
index.tmpl文件内容:
{{ .title }}
{{ .content }}
自定义渲染分隔符:
默认是用两个大括号隔开,你也可是设置成别的:
r := gin.Default()
r.Delims("{", "}")
r.LoadHTMLGlob("/templates")
自定义函数在模板中使用:
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}
func main() {
router := gin.Default()
router.Delims("{", "}")
router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
router.LoadHTMLGlob("templates/*")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", map[string]interface{}{
"time": time.Date(2019, 06, 16, 10, 0, 0, 0, time.UTC),
})
})
router.Run(":8080")
}
index.tmpl文件内容:
{.time | formatAsDate}
发布HTTP重定向很容易,支持内部和外部链接:
router.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/index")
})
这一篇先介绍一些常用的使用,更多的 api 使用我们后面用到的时候再详细的介绍。