在本文中,我们将详细介绍6个最好的Go语言Web框架的各个方面,并为您提供关于go语言web框架推荐的相关解答,同时,我们也将为您带来关于C语言WEB框架小进展-静态文件返回、go语言web开发框架—
在本文中,我们将详细介绍6个最好的Go语言Web框架的各个方面,并为您提供关于go语言web框架推荐的相关解答,同时,我们也将为您带来关于C语言WEB框架小进展-静态文件返回、go语言web开发框架—gin框架入门、Go语言web框架 gin、Go语言WEB框架(Gin)详解的有用知识。
本文目录一览:6个最好的Go语言Web框架(go语言web框架推荐)
译者注:本文介绍截至目前(2017年)最好的6个Go语言Web框架。以下为译文。
如果你是自己写一个小应用程序,那你可能不需要Web框架。但是如果你要做产品,那么你肯定需要一个好的框架。
如果你认为你有相应的知识和经验,你会自己编写所有的这些代码么?你有时间找到一个产品级的外部包来完成工作吗?你确定这与你应用程序的其它部分一致吗?
这些都是促使我们(即便是我们中最优秀的)使用框架的原因,如果其他人已经做了必要的艰苦的工作,我们不会想让自己重复这些工作。
Go 是一个快速增长的开源编程语言,用于构建简单、快速和可靠的软件。点这里看有哪些大公司在使用Go语言来构建他们的服务。
本文提供了所有必要的信息,以帮助开发人员了解使用Go语言开发Web应用程序的最佳选项。。
本文包含了最详细的框架比较,通过尽可能多的角度(人气,社区支持,内置功能等)来比较最知名的几个Web 框架。
Beego: 一个Go语言下开源的,高性能Web框架
https://github.com/astaxie/beego
https://beego.me
Buffalo: 一个Go语言下快速Web开发框架
https://github.com/gobuffalo/buffalo
https://gobuffalo.io
Echo: 一个高性能,极简的Web框架
https://github.com/labstack/echo
https://echo.labstack.com
Gin: 一个Go语言写的HTTP Web框架。它提供了Martini风格的API并有更好的性能。
https://github.com/gin-gonic/gin
https://gin-gonic.github.io/gin
Iris: 目前发展最快的Go Web框架。提供完整的MVC功能并且面向未来。
https://github.com/kataras/iris
https://iris-go.com
Revel: 一个高生产率,全栈Go语言的Web框架。
https://github.com/revel/revel
https://revel.github.io
按人气排序(star收藏数)
感谢 astaxie 和 kataras 的精彩工作,同时希望其他的框架能够赶上并提供更多的用例,至少对我来说,如果要我切换到一个新框架,用例是快速掌握更多知识的最丰富的资源。一个用例抵得上千言万语。
按功能由多到少排序
Go中最著名的“Web框架”并不是真正的框架,也就是说:Echo、Gin和Bufflo不是真正的(完整功能的)Web框架。但是Go社区的大多数人认为它们是。他们认为它们可以和Iris、Beego或Revel相比较。因此,我们有义务将它们也包括在这个列表中。
除了Beego和Revel之外,上述所有框架都可以适应任何为net/http创建的中间件。有些框架很容易,有些需要些编码(即使有点痛苦也是一个选择)。
路由:命名路径参数和通配符(Router: Named Path Parameters & Wildcard)
你可以注册一个处理器(handler)并对应一个动态路径路由(router)。
下面是命名路径参数的例子:
"/user/{username}" matches to "/user/me", "/user/speedwheel" etc
路径参数 "username"的值分别是 "/me"和 "speedwheel"。
下面是通配符的例子:
"/user/{path *wildcard}" matches to
"/user/some/path/here",
"/user/this/is/a/dynamic/multi/level/path" etc
路径参数 path 的值分别是 "some/path/here" 和 "this/is/a/dynamic/multi/level/path"。
Iris 也支持一种叫 macros 的功能,可以描述为 /user/{username:string} 或 /user/{username:int min(1)}
路由:正则表达式(Router: Regex)
你可以注册一个处理器(handler)并对应一个包含过滤器(filter)的动态路径路由(router)。过滤器会过滤掉一些传给处理器的参数值。
下面是一个例子:
"/user/{id ^[0-9]$}" matches to "/user/42" but not to "/user/somestring"
路径参数 id 的值是整数 42 (而不会是字符串)。
路由:分组(Router: Grouping)
你可以注册通用逻辑或中间件/处理器(middlewar/handler)并对应一组共享相同路径前缀的路由(router)。
下面是一个例子:
myGroup := Group("/user", userAuthenticationMiddleware)
myGroup.Handle("GET", "/", userHandler)
myGroup.Handle("GET", "/profile", userProfileHandler)
myGroup.Handle("GET", "/signup", getUserSignupForm)
/user
/user/profile
/user/signup
你甚至可以在分组(group)中再创建子分组(subgroup)
myGroup.Group("/messages", optionalUserMessagesMiddleware)
myGroup.Handle("GET'', "/{id}", getMessageByID)
/user/messages/{id}
路由:随意组合以上选项而不用担心冲突(Router: All the above Mixed Without Conflict)
这是一个先进且很有用的功能,我们很多人希望路由或Web框架支持该功能,但目前在Go环境里只有Iris支持。
这意味着像 /{path *wildcard},
/user/{username}, /user/static
和 /user/{path *wildcard} 可以注册在同一个路由里而且可以被正确地映射到静态路径 (/user/static) 或 通配符 (/{path *wildcard})
路由:自定义HTTP错误(Router: Custom HTTP Errors)
你可以注册一个处理器(handler)并对应一个’错误’代码。 HTTP 错误代码是一个 >=400 的状态码,例如 NotFound 404。
下面是一个例子:
OnErrorCode(404, myNotFoundHandler)
上面的大多数Web框架只支持注册 404,405 和 500 错误代码,但是像 Iris,
Beego和 Revel 这些提供完整功能的框架支持任何状态代码甚至 任何错误(any error)代码(只有Iris支持 任何错误 )。
100%与 net/http 兼容(100%
compatible with net/http)
这意味着:
框架提供了上下文(context)让你可以直接访问 *http.Request和
和 http.ResponseWriter。
你可以把 net/http 处理器(handler)转化到一个特定框架下的处理器(Handler)。
中间件生态系统(Middleware ecosystem)
你可以不用自己来为每个处理器包装中间件,但是框架提供给你一个完整的引擎来定义流程,无论是全局的或每个路由或每组路由,例如 Use(middleware), Done(middleware) 等。
Sinatra风格的API(Sinatra-like API)
在运行时注册处理器来处理特定HTTP方法的路由(和路径参数)。
下面是一个例子:
.Get or GET("/path", gethandler)
.Post or POST("/path", postHandler)
.Put or PUT("/path", putHandler) and etc.
服务器: 自动HTTPS(Server: Automatic HTTPS)
框架的服务器支持注册和自动更新SSL证书来管理SSL/TLS传入连接(https)。最着名的自动HTTPS提供者是letsencrypt。
服务器: 正常关机(Server: Gracefully Shutdown)
当按下 CTRL+C 关闭终端应用程序时,服务器将正常地停止,它会等待一些连接完成它们的工作(在设定的时间内),或者触发一个自定义的事件来做清理(例如关闭数据库)。
服务器: 多监听器(Server: Multi Listeners)
框架的服务器支持注册自定义 net.Listener 或者可以通过多个 http 服务器和地址来服务web应用。
完全支持HTTP/2(Full HTTP/2)
框架支持HTTP/2,包括https和服务器 Push 功能。
子域(Subdomains)
你可以直接在你的Web应用里按子域(subdomain) 直接注册路由。
secondary 是指框架不支持该功能但是你依然可以通过启用多个http服务器来实现。坏处是主应用程序和子域并不相连而且默认情况下它们并不直接共享逻辑。
会话(Sessions)
http会话被支持并可以在你的特定处理器中使用。
一些Web框架支持使用后台数据库来存储会话,以便在服务器重启之间获得持久性。
Buffalo 使用 gorrila 会话,这比其他的实现要慢一点点。
下面是一个例子:
func setValue(context http_context){
s := Sessions.New(http_context)
s.Set("key", "my value")
}
func getValue(context http_context){
s := Sessions.New(http_context)
myValue := s.Get("key")
}
func logoutHandler(context http_context){
Sessions.Destroy(http_context)
}
Wiki: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP_session
Websockets
框架支持websocket通信协议。实现是各不相同的。
你应该搜索他们的例子,看看有什么适合你。我的同事尝试了所有框架后告诉我,与其他框架相比,Iris实现了功能更强大且更简单的webosocket连接。
Wiki: https://en.wikipedia.org/wiki/WebSocket
App 内置视图/模板(View/Templates)
通常情况下,你必须将所有模板文件与你的Web应用程序的可执行文件一起打包。应用程序App 内置意味着该框架支持与go-bindata的集成,所以最终的可执行文件包含模板,表示为 []byte。
什么是视图引擎
框架支持模板加载,模板自定义和自带模板并能在一些关键工作上帮助我们。
视图引擎:STD(View Engine: STD)
框架支持标准 html/template 解析器来加载模板。
视图引擎:Pug(View Engine: Pug)
框架支持 Pug 解析器来加载模板。
视图引擎:Django(View Engine: Django)
框架支持 Django 解析器来加载模板。
视图引擎:Handlebars(View Engine: Handlebars)
框架支持 Handlebars 解析器来加载模板。
视图引擎:Amber(View Engine: Amber)
框架支持 Amber 解析器来加载模板。
渲染器:Markdown, JSON, JSONP, XML…
框架的上下文为你提供了一种轻松地发送和定制各种内容类型的响应结果的简便方法。
MVC
模型-视图-控制器(MVC)是在计算机上实现用户界面的软件架构模式。它将一个给定的应用程序分成三个相互关联的部分。这样做是为了将信息的内部表示与信息呈现给用户并让用户接受的方式分离开来。MVC设计模式分离了这些主要成分并允许高效的代码重用和并行开发。
Iris支持完整的MVC功能,可以在运行时注册。
Beego仅支持方法和模型匹配,可以在运行时注册。
Revel支持方法、路径和模型匹配,只能通过一个生成器注册(一个用于构建Web应用程序的必要软件)。
Wiki: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
缓存(Caching)
Web缓存(或HTTP缓存)是一种信息技术,用于临时存储(缓存)Web文档,如HTML页面和图像,以减少服务器延迟。Web缓存系统记录了网络通信,如果满足某些条件,后续请求的结果可以直接取自Web缓存。Web缓存系统既可以指设备,也可以指计算机程序。
Wiki: https://en.wikipedia.org/wiki/Web_cache
文件服务器(File Server)
你可以把一个(物理)目录注册到一个路由表,该路由表会自动将目录下的文件服务给客户程序。
文件服务器: 内置入APP(File Server: Embedded Into App)
通常你必须将所有静态文件(如资源文件、CSS、JavaScript文件…)和应用程序的可执行文件一起传输。支持此特性的框架使你有机会将所有这些数据嵌入到应用程序中,表示为 []byte,它们的响应时间也更快,因为服务器可以不用在物理位置上查找文件而直接服务。
响应可以在发送之前在生命周期内多次修改(Response can be Modified Many times through lifecycle before sent)
目前仅Iris可以通过http_context内置的 response writer 支持该功能。
当框架支持这一功能时,你可以在发送给客户端之前检索,重置或修改的状态代码、正文和头文件(在基于net/http的Web框架中,默认情况下这是不可能的,因为正文和状态代码在写入后无法检索或更改)。
Gzip
你可以在路由的处理器里改变响应writer来使用gzip压缩,框架应该设置返回结果的头(header),并在出现任何错误时重置writer,也应该检查客户端是否支持gzip。
gzip是一种文件格式(也可以是一个软件应用),用于文件的压缩和解压缩软件。
Wiki: https://en.wikipedia.org/wiki/Gzip
测试框架(Testing Framework)
你可以使用特定的框架测试HTTP,测试框架就是帮助你轻松地编写更好的测试。
下面是一个例子(目前仅Iris支持)
func TestAPI(t *testing.T) {
app := myIrisApp()
tt := httptest.New(t, app)
tt.GET("/admin").WithBasicAuth("name", "pass").Expect().
Status(httptest.StatusOK).Body().Equal("welcome")
}
myirisapp 返回一个你假定的Web应用程序,
针对路径 /admin 它有一个GET处理器并有基本的身份验证保护。
上面简单的测试检查 /admin 请求是否返回状态码 Status OK 并验证特定的用户名和密码,最后检查正文内容是 “welcome”。
Typescript Transpiler
Typescript的目标是成为一个ES6超集,除了标准定义的所有新东西,它将添加一个静态类型系统(static type system)。Typescript也有一个转换器(transpiler)将我们的Typescript代码(即6 +类型)转换到ES5或ES3标准上的JavaScript代码,以便在目前的浏览器上运行。
在线编辑器(Online Editor)
有了在线编辑器,你可以快速方便地编译和运行Go代码。
日志系统(Logging System)
自定义日志系统系统可以扩展原始日志包的功能,比如代码配色、格式、日志级别的分隔,不同的登录后台等等。
维护和自动更新(Maintenance & Auto-Updates)
以非侵入性的方式通知用户“即时更新”。
本文由“壹伴编辑器”提供技术支持
本文来自dev网Edward Marinescu的博客,并由roy翻译,译文地址如下:
https://blog.csdn.net/dev_csdn/article/details/78740990?from=singlemessage&isappinstalled=0
原文可点击左下角 阅读原文 查阅,侵删。
本文由“壹伴编辑器”提供技术支持
推荐阅读:
来分享一波学习资料
推荐一款超好用的工具
四款神器,教你笑傲江湖
历史文章:
Go语言常见坑
为什么在Go语言中要慎用interface{}
GitHub上优秀的Go开源项目
利用golang优雅的实现单实例
golang并发编程之互斥锁、读写锁详解
go语言nil和interface详解
vmware上安装linux过程记录
NAT模式实现虚拟机共享主机网络
我是小碗汤,我们一起学习。
扫码关注,精彩内容第一时间推给你
本文分享自微信公众号 - 我的小碗汤(mysmallsoup)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
C语言WEB框架小进展-静态文件返回
最近又将这个项目拿出来玩,中间已经搁置很久很久了。
这方面的经验还是严重缺失,一直在重构代码,改着改着就又重构调整。
难
最近新进展是修复了之前线程管理的BUG,加上能简单返回静态资源了
go语言web开发框架—gin框架入门
快速入门
- 安装并配置GO工作区
- 在GOPATH的src目录下新建项目文件夹
- 使用
go mod init
命令初始化项目 - 使用
go get -u github.com/gin-gonic/gin
命令安装gin - 示例代码:
package main
import "github.com/gin-gonic/gin"
func main() {
// 创建默认路由
r := gin.Default()
// 绑定路由规则和执行函数,当访问/ping接口时就会执行后面的函数
r.GET("/ping", func(c *gin.Context) {
// 该函数直接返回JSON字符串
c.JSON(200, gin.H{
"message": "pong",
})
})
// 启动并监听端口,默认8080,可使用:r.Run(":8000")修改监听端口
r.Run()
}
- 浏览器或postman访问:
localhost:8080/ping
,即可成功返回json字符串
路由
API参数
在路径中使用/:paramName
或者/*paramName
作为占位符,可在代码中通过*gin.Context
的param
方法获取到API参数,使用冒号占位符获取到的API参数不包含路径中的/
,使用星号占位符则包含/
func main() {
// 创建路由
r := gin.Default()
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
c.JSON(http.StatusOK, gin.H{
"name":name,
"action":action,
})
})
// 默认端口号是8080
r.Run(":8000")
}
使用postman请求http://127.0.0.1:8000/user/lee/index.html
,返回的json格式如下:
{
"action": "/index.html",
"name": "lee"
}
URL参数
使用DefaultQuery或者Query方法可以获取前端传递的URL参数
DefaultQuery:可指定默认值
Query:不可指定默认值
func main() {
// 创建路由
r := gin.Default()
r.GET("/add_user", func(c *gin.Context) {
name := c.Query("name")
age := c.DefaultQuery("age", "23")
c.JSON(http.StatusOK, gin.H{
"name":name,
"age":age,
})
})
// 默认端口号是8080
r.Run(":8000")
}
postman访问:http://127.0.0.1:8000/add_user?name=lee&age=18
,返回如下:
{
"age": "18",
"name": "lee"
}
postma访问:http://127.0.0.1:8000/add_user?name=lee
,返回如下:
{
"age": "23",
"name": "lee"
}
表单参数
表单传输为post请求,http常见的传输格式为四种:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数,还可以使用DefaultPostForm方法指定默认值获取。
func main() {
// 创建路由
r := gin.Default()
r.POST("/form_post", func(c *gin.Context) {
message := c.DefaultPostForm("message", "default message")
name := c.PostForm("name")
c.JSON(http.StatusOK, gin.H{
"message":message,
"name":name,
})
})
// 默认端口号是8080
r.Run(":8000")
}
使用postman发送请求如下所示:
上传单个文件
func main() {
// 创建路由
r := gin.Default()
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
fmt.Println(err)
}
c.SaveUploadedFile(file, file.Filename)
c.JSON(http.StatusOK, gin.H{
"message":"ok",
"fileName":file.Filename,
})
})
// 默认端口号是8080
r.Run(":8000")
}
使用postman发送请求如下:
上传特定文件
func main() {
// 创建路由
r := gin.Default()
r.POST("/upload", func(c *gin.Context) {
_, file, err := c.Request.FormFile("file")
if err != nil {
fmt.Println(err)
}
if file.Size >= 1024 * 1024 * 2 {
c.JSON(http.StatusOK, gin.H{
"message":"文件大小不能超过2M",
})
return
}
if file.Header.Get("Content-Type") != "image/png" {
c.JSON(http.StatusOK, gin.H{
"message":"只能上传png格式文件",
})
return
}
c.SaveUploadedFile(file, file.Filename)
c.JSON(http.StatusOK, gin.H{
"message":"ok",
"fileName":file.Filename,
})
})
// 默认端口号是8080
r.Run(":8000")
}
使用postman发送请求如下:
上传多个文件
func main() {
// 创建路由
r := gin.Default()
r.POST("/upload", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
fmt.Println(err)
return
}
files := form.File["files"]
for _, file := range files {
err := c.SaveUploadedFile(file, file.Filename)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"message":"文件上传失败",
"err":err,
})
return
}
}
c.JSON(http.StatusOK, gin.H{
"message":"文件上传成功",
"upload file count":len(files),
})
})
// 默认端口号是8080
r.Run(":8000")
}
使用postman调用接口如下:
路由组
路由组(RouterGroup),用于管理一组路由,在不同路由组中的路由可重名
func main() {
// 创建路由
r := gin.Default()
v1 := r.Group("/v1")
v1.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message":"/v1/hello",
})
})
v2 := r.Group("/v2")
v2.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message":"/v2/hello",
})
})
// 默认端口号是8080
r.Run(":8000")
}
postman分别访问http://127.0.0.1:8000/v1/hello
和http://127.0.0.1:8000/v2/hello
,返回不同的消息
自定义404
定义NoRoute之后,如果访问的地址不在已经存在的路由中,就会默认执行这个配置的方法,可用于配置404返回信息
func main() {
// 创建路由
r := gin.Default()
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"message":"页面丢了!",
})
})
// 默认端口号是8080
r.Run(":8000")
}
使用postman调用结果如下:
数据绑定
JSON
将前端发送的json数据绑定到结构体上,在结构体中除了定义结构体的参数之外,在参数后面需要定义json的参数名,表示将json中的哪个参数与结构体中的参数进行绑定,binding:"required"
表示该字段为必传,如果为空则报错
func main() {
// 创建路由
r := gin.Default()
r.GET("/loginJSON", func(c *gin.Context) {
var login LoginForm
err := c.ShouldBindJSON(&login)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 判断用户名密码是否正确
if login.User != "root" || login.Password != "admin" {
c.JSON(http.StatusBadRequest, gin.H{
"status": "304",
"message":"登录失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "200",
"message":"登录成功",
})
})
// 默认端口号是8080
r.Run(":8000")
}
type LoginForm struct {
// 表明该属性与传递的json的user属性进行绑定
User string `json:"user" binding:"required"`
Password string `json:"password" binding:"required"`
}
使用postman调用接口结果如下:
表单
func main() {
// 创建路由
r := gin.Default()
r.POST("/loginForm", func(c *gin.Context) {
// 声明接收的变量
var form LoginForm
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.Bind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "root" || form.Password != "admin" {
c.JSON(http.StatusBadRequest, gin.H{
"status": "304",
"message":"登录失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "200",
"message":"登录成功",
})
})
// 默认端口号是8080
r.Run(":8000")
}
type LoginForm struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
使用postman调用接口结果如下:
URI
func main() {
// 创建路由
r := gin.Default()
r.GET("/login/:user/:password", func(c *gin.Context) {
// 声明接收的变量
var login LoginForm
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.ShouldBindUri(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if login.User != "root" || login.Password != "admin" {
c.JSON(http.StatusBadRequest, gin.H{
"status": "304",
"message":"登录失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "200",
"message":"登录成功",
})
})
// 默认端口号是8080
r.Run(":8000")
}
type LoginForm struct {
User string `form:"user" json:"user" uri:"user" binding:"required"`
Password string `form:"password" json:"password" uri:"password" binding:"required"`
}
使用postman调用结果如下:
数据响应
JSON、Struct、XML、YAML
func main() {
// 创建路由
r := gin.Default()
// JSON响应
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message":"JSON OK",
})
})
// 结构体响应
r.GET("/someStruct", func(c *gin.Context) {
type Msg struct {
Message string
}
msg := Msg{
Message: "Struct OK",
}
c.JSON(http.StatusOK, msg)
})
// XML响应
r.GET("/someXML", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{
"message":"XML OK",
})
})
// YAML响应
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{
"message":"YAML OK",
})
})
// 默认端口号是8080
r.Run(":8000")
}
不同响应方式返回示例:
// JSON返回
{
"message": "JSON OK"
}
// Struct返回
{
"Message": "Struct OK"
}
<!-- XML返回 -->
<map>
<message>XML OK</message>
</map>
# YAML返回
message: YAML OK
重定向
func main() {
// 创建路由
r := gin.Default()
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})
// 默认端口号是8080
r.Run(":8000")
}
重定向之后当使用浏览器访问http://127.0.0.1:8000/redirect
链接则会自动跳转到百度首页
同步异步
func main() {
// 创建路由
r := gin.Default()
r.GET("/async", func(c *gin.Context) {
copyContext := c.Copy()
go func() {
time.Sleep(time.Second * 3)
log.Println("异步执行:" + copyContext.Request.URL.Path)
}()
c.JSON(http.StatusOK, gin.H{
"message":"OK",
})
})
r.GET("/sync", func(c *gin.Context) {
time.Sleep(3 * time.Second)
log.Println("同步执行:" + c.Request.URL.Path)
c.JSON(http.StatusOK, gin.H{
"message":"OK",
})
})
// 默认端口号是8080
r.Run(":8000")
}
在异步中,访问接口,会直接返回message:OK
响应,然后后台在3秒之后打印异步执行提示消息
在同步中,访问接口,需要等待3秒之后才会打印并响应
中间件
全局中间件
定义全局中间件之后,所有的请求都会经过该中间件
func main() {
// 创建路由
r := gin.Default()
// 注册中间件
r.Use(MiddleWare())
// 大括号只是为了代码规范,没有大括号也不会有问题
{
r.GET("/middle", func(c *gin.Context) {
fmt.Println("接口执行了..")
value, _ := c.Get("middleParam")
c.JSON(http.StatusOK, gin.H{
"middleParam":value,
})
})
}
// 默认端口号是8080
r.Run(":8000")
}
// 定义中间件
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
fmt.Println("中间件执行了..")
// 将变量到Context中,可以通过Get(key)取
c.Set("middleParam", "middle")
// 执行函数
c.Next()
// 中间件执行完后续操作
duration := time.Since(start)
fmt.Println("执行中间件耗时:", duration)
}
}
在代码中定义了一个中间件,并在中间件中放入了middleParam参数,然后在接口中可以将该参数取出,并且中间件总是会在接口执行之前执行。
使用postman调用接口如下:
日志输出如下:
局部中间件
局部中间件仅在当前接口有用。
func main() {
// 创建路由
r := gin.Default()
r.GET("/middle_local", MiddleWare(), func(c *gin.Context) {
value, _ := c.Get("middleParam")
c.JSON(http.StatusOK, gin.H{
"middleParam":value,
})
})
// 默认端口号是8080
r.Run(":8000")
}
Cookie
Cookie介绍
- HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出,Cookie就是解决HTTP协议无状态的方案之一
- Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求。
- Cookie由服务器创建,并发送给浏览器,最终由浏览器保存。
Cookie使用
func main() {
// 创建路由
r := gin.Default()
r.GET("/cookie", func(c *gin.Context) {
// 获取客户端是否携带cookie
cookie, err := c.Cookie("cookie_key")
if err != nil {
cookie = "NotSet"
// 给客户端设置cookie
// maxAge:有效期,单位为秒
// path:cookie所在目录
// domain:域名
// secure:是否只能通过https访问
// httpOnly:是否允许别人通过js获取自己的cookie
c.SetCookie("cookie_key", "cookie_value", 60, "/", "localhost", false, true)
}
fmt.Printf("cookie的值是: %s\n", cookie)
})
// 默认端口号是8080
r.Run(":8000")
}
参数校验
Struct结构体校验
type Person struct {
// name必填
Name string `form:"name" binding:"required"`
// age必填并且大于10
Age int `form:"age" binding:"required,gt=10"`
// birthday格式化"2006-01-02"
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
// 创建路由
r := gin.Default()
r.GET("/struct_verify", func(c *gin.Context) {
var person Person
err := c.ShouldBind(&person)
if err != nil {
fmt.Println("Error:", err)
c.String(http.StatusBadRequest, fmt.Sprint(err))
return
}
c.String(http.StatusOK, fmt.Sprint(person))
})
// 3.监听端口,默认在8080
// Run("里面不指定端口号默认为8080")
r.Run(":8000")
}
当name不传,age小于10时返回结果如下:
Key: ''Person.Name'' Error:Field validation for ''Name'' failed on the ''required'' tag
Key: ''Person.Age'' Error:Field validation for ''Age'' failed on the ''gt'' tag
日志
func main() {
// 禁用颜色显示
gin.DisableConsoleColor()
// 新建日志文件
f, _ := os.Create("gin.log")
// 将日志写入到文件中
//gin.DefaultWriter = io.MultiWriter(f)
// 同时将日志写入文件和控制台
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
// Run("里面不指定端口号默认为8080")
r.Run(":8000")
}
本文参与了思否技术征文,欢迎正在阅读的你也加入。
Go语言web框架 gin
gin是go语言环境下的一个web框架, 它类似于Martini, 官方声称它比Martini有更好的性能, 比Martini快40倍, Ohhhh….看着不错的样子, 所以就想记录一下gin的学习. gin的github代码在这里: gin源码. gin的效率获得如此突飞猛进, 得益于另一个开源项目httprouter, 项目地址: httprouter源码. 下面主要记录一下gin的使用.
###1. 安装gin 使用命令go get github.com/gin-gonic/gin
就可以. 我们使用gin的时候引入相应的包就OKimport "github.com/gin-gonic/gin"
.
###2. 使用方法
<1> 一种最简单的使用GET/POST方法
gin服务端代码是:
// func1: 处理最基本的GET
func func1 (c *gin.Context) {
// 回复一个200OK,在client的http-get的resp的body中获取数据
c.String(http.StatusOK, "test1 OK")
}
// func2: 处理最基本的POST
func func2 (c *gin.Context) {
// 回复一个200 OK, 在client的http-post的resp的body中获取数据
c.String(http.StatusOK, "test2 OK")
}
func main(){
// 注册一个默认的路由器
router := gin.Default()
// 最基本的用法
router.GET("/test1", func1)
router.POST("/test2", func2)
// 绑定端口是8888
router.Run(":8888")
}
客户端代码是:
func main(){
// 调用最基本的GET,并获得返回值
resp,_ := http.Get("http://0.0.0.0:8888/test1")
helpRead(resp)
// 调用最基本的POST,并获得返回值
resp,_ = http.Post("http://0.0.0.0:8888/test2", "",strings.NewReader(""))
helpRead(resp)
}
在服务端, 实例化了一个router, 然后使用GET和POST方法分别注册了两个服务, 当我们使用HTTP GET方法的时候会使用GET注册的函数, 如果使用HTTP POST的方法, 那么会使用POST注册的函数. gin支持所有的HTTP的方法例如: GET, POST, PUT, PATCH, DELETE 和 OPTIONS等. 看客户端中的代码, 当调用http.Get("http://0.0.0.0:8888/test1")
的时候, 服务端接收到请求, 并根据/test1将请求路由到func1函数进行 处理. 同理, 调用http.Post("http://0.0.0.0:8888/test2", "",strings.NewReader(""))
时候, 会使用func2函数处理. 在func1和func2中, 使用gin.Context填充了一个String的回复. 当然也支持JSON, XML, HTML等其他一些格式数据. 当执行c.String或者c.JSON时, 相当于向http的回复缓冲区写入了 一些数据. 最后调用router.Run(“:8888”)开始进行监听,Run的核心代码是:
func (engine *Engine) Run(addr string) (err error) {
debugPrint("Listening and serving HTTP on %s\n", addr)
defer func() { debugPrintError(err) }()
// 核心代码
err = http.ListenAndServe(addr, engine)
return
}
其本质就是http.ListenAndServe(addr, engine).
注意: helpRead函数是用于读取response的Body的函数, 你可以自己定义, 本文中此函数定义为:
// 用于读取resp的body
func helpRead(resp *http.Response) {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("ERROR2!: ", err)
}
fmt.Println(string(body))
}
<2> 传递参数
传递参数有几种方法, 对应到gin使用几种不同的方式来解析.
**第一种:** 使用gin.Context中的Param方法解析
对应的服务端代码为:
// func3: 处理带参数的path-GET
func func3(c *gin.Context) {
// 回复一个200 OK
// 获取传入的参数
name := c.Param("name")
passwd := c.Param("passwd")
c.String(http.StatusOK, "参数:%s %s test3 OK", name, passwd)
}
// func4: 处理带参数的path-POST
func func4(c *gin.Context) {
// 回复一个200 OK
// 获取传入的参数
name := c.Param("name")
passwd := c.Param("passwd")
c.String(http.StatusOK, "参数:%s %s test4 OK", name, passwd)
}
// func5: 注意'':''和''*''的区别
func func5(c *gin.Context) {
// 回复一个200 OK
// 获取传入的参数
name := c.Param("name")
passwd := c.Param("passwd")
c.String(http.StatusOK, "参数:%s %s test5 OK", name, passwd)
}
func main(){
router := gin.Default()
// TODO:注意'':''必须要匹配,''*''选择匹配,即存在就匹配,否则可以不考虑
router.GET("/test3/:name/:passwd", func3)
router.POST("/test4/:name/:passwd", func4)
router.GET("/test5/:name/*passwd", func5)
router.Run(":8888")
}
客户端测试代码是:
func main() {
// GET传参数,使用gin的Param解析格式: /test3/:name/:passwd
resp,_ = http.Get("http://0.0.0.0:8888/test3/name=TAO/passwd=123")
helpRead(resp)
// POST传参数,使用gin的Param解析格式: /test3/:name/:passwd
resp,_ = http.Post("http://0.0.0.0:8888/test4/name=PT/passwd=456", "",strings.NewReader(""))
helpRead(resp)
// 注意Param中'':''和''*''的区别
resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/passwd=789")
helpRead(resp)
resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/")
helpRead(resp)
}
注意上面定义参数的方法有两个辅助符号: ‘:’和’*’. 如果使用’:’参数方法, 那么这个参数是必须要匹配的, 例如上面的router.GET(“/test3/:name/:passwd”, func3), 当请求URL是 类似于http://0.0.0.0:8888/test3/name=TAO/passwd=123这样的参会被匹配, 如果是http://0.0.0.0:8888/test3/name=TAO 或者http://0.0.0.0:8888/test3/passwd=123是不能匹配的. 但是如果使用’*‘参数, 那么这个参数是可选的. router.GET(“/test5/:name/*passwd”, func5) 可以匹配http://0.0.0.0:8888/test5/name=TAO/passwd=789, 也可以匹配http://0.0.0.0:8888/test5/name=TAO/. 需要注意的一点是, 下面这个URL是不是能够 匹配呢? http://0.0.0.0:8888/test5/name=TAO, 注意TAO后面没有’/’, 这个其实就要看有没有一个路由是到http://0.0.0.0:8888/test5/name=TAO路径的, 如果有, 那么指定的那个函数进行处理, 如果没有http://0.0.0.0:8888/test5/name=TAO会被重定向到http://0.0.0.0:8888/test5/name=TAO/, 然后被当前注册的函数进行处理.
**第二种:** 使用gin.Context中的Query方法解析
这个类似于正常的URL中的参数传递, 先看服务端代码:
// 使用Query获取参数
func func6(c *gin.Context) {
// 回复一个200 OK
// 获取传入的参数
name := c.Query("name")
passwd := c.Query("passwd")
c.String(http.StatusOK, "参数:%s %s test6 OK", name, passwd)
}
// 使用Query获取参数
func func7(c *gin.Context) {
// 回复一个200 OK
// 获取传入的参数
name := c.Query("name")
passwd := c.Query("passwd")
c.String(http.StatusOK, "参数:%s %s test7 OK", name, passwd)
}
func main(){
router := gin.Default()
// 使用gin的Query参数形式,/test6?firstname=Jane&lastname=Doe
router.GET("/test6", func6)
router.POST("/test7", func7)
router.Run(":8888")
}
客户端测试代码是:
func main() {
// 使用Query获取参数形式/test6?firstname=Jane&lastname=Doe
resp,_ = http.Get("http://0.0.0.0:8888/test6?name=BBB&passwd=CCC")
helpRead(resp)
resp,_ = http.Post("http://0.0.0.0:8888/test7?name=DDD&passwd=EEE", "",strings.NewReader(""))
helpRead(resp)
}
这种方法的参数也是接在URL后面, 形如http://0.0.0.0:8888/test6?name=BBB&passwd=CCC. 服务器可以使用name := c.Query(“name”)这种 方法来解析参数.
**第三种:** 使用gin.Context中的PostForm方法解析
我们需要将参数放在请求的Body中传递, 而不是URL中. 先看服务端代码:
// 参数是form中获得,即从Body中获得,忽略URL中的参数
func func8(c *gin.Context) {
message := c.PostForm("message")
extra := c.PostForm("extra")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(200, gin.H{
"status": "test8:posted",
"message": message,
"nick": nick,
"extra": extra,
})
}
func main(){
router := gin.Default()
// 使用post_form形式,注意必须要设置Post的type,
// 同时此方法中忽略URL中带的参数,所有的参数需要从Body中获得
router.POST("/test8", func8)
router.Run(":8888")
}
客户端代码是:
func main() {
// 使用post_form形式,注意必须要设置Post的type,同时此方法中忽略URL中带的参数,所有的参数需要从Body中获得
resp,_ = http.Post("http://0.0.0.0:8888/test8", "application/x-www-form-urlencoded",strings.NewReader("message=8888888&extra=999999"))
helpRead(resp)
}
由于我们使用了request Body, 那么就需要指定Body中数据的形式, 此处是form格式, 即application/x-www-form-urlencoded. 常见的几种http提交数据方式有: application/x-www-form-urlencoded; multipart/form-data; application/json; text/xml. 具体使用请google.
在服务端, 使用message := c.PostForm(“message”)方法解析参数, 然后进行处理.
<3> 传输文件
下面测试从client传输文件到server. 传输文件需要使用multipart/form-data格式的数据, 所有需要设定Post的类型是multipart/form-data.
首先看服务端代码:
// 接收client上传的文件
// 从FormFile中获取相关的文件data!
// 然后写入本地文件
func func9(c *gin.Context) {
// 注意此处的文件名和client处的应该是一样的
file, header , err := c.Request.FormFile("uploadFile")
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.String(http.StatusOK, "upload file success")
}
func main(){
router := gin.Default()
// 接收上传的文件,需要使用
router.POST("/upload", func9)
router.Run(":8888")
}
客户端代码是:
func main() {
// 上传文件POST
// 下面构造一个文件buf作为POST的BODY
buf := new(bytes.Buffer)
w := multipart.NewWriter(buf)
fw,_ := w.CreateFormFile("uploadFile", "images.png") //这里的uploadFile必须和服务器端的FormFile-name一致
fd,_ := os.Open("images.png")
defer fd.Close()
io.Copy(fw, fd)
w.Close()
resp,_ = http.Post("http://0.0.0.0:8888/upload", w.FormDataContentType(), buf)
helpRead(resp)
}
首先客户端本地需要有一张”images.png”图片, 同时需要创建一个Form, 并将field-name命名为”uploadFile”, file-name命名为”images.png”. 在服务端, 通过”uploadFile”可以得到文件信息. 客户端继续将图片数据copy到创建好的Form中, 将数据数据Post出去, 注意数据的类型指定! 在服务端, 通过file, header , err := c.Request.FormFile(“uploadFile”)获得文件信息, file中就是文件数据, 将其拷贝到本地文件, 完成文件传输.
<4> binding数据
gin内置了几种数据的绑定例如JSON, XML等. 简单来说, 即根据Body数据类型, 将数据赋值到指定的结构体变量中. (类似于序列化和反序列化)
看服务端代码:
// Binding数据
// 注意:后面的form:user表示在form中这个字段是user,不是User, 同样json:user也是
// 注意:binding:"required"要求这个字段在client端发送的时候必须存在,否则报错!
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
// bind JSON数据
func funcBindJSON(c *gin.Context) {
var json Login
// binding JSON,本质是将request中的Body中的数据按照JSON格式解析到json变量中
if c.BindJSON(&json) == nil {
if json.User == "TAO" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"JSON=== status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"JSON=== status": "unauthorized"})
}
} else {
c.JSON(404, gin.H{"JSON=== status": "binding JSON error!"})
}
}
// 下面测试bind FORM数据
func funcBindForm(c *gin.Context) {
var form Login
// 本质是将c中的request中的BODY数据解析到form中
// 方法一: 对于FORM数据直接使用Bind函数, 默认使用使用form格式解析,if c.Bind(&form) == nil
// 方法二: 使用BindWith函数,如果你明确知道数据的类型
if c.BindWith(&form, binding.Form) == nil{
if form.User == "TAO" && form.Password == "123" {
c.JSON(http.StatusOK, gin.H{"FORM=== status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"FORM=== status": "unauthorized"})
}
} else {
c.JSON(404, gin.H{"FORM=== status": "binding FORM error!"})
}
}
func main(){
router := gin.Default()
// 下面测试bind JSON数据
router.POST("/bindJSON", funcBindJSON)
// 下面测试bind FORM数据
router.POST("/bindForm", funcBindForm)
// 下面测试JSON,XML等格式的rendering
router.GET("/someJSON", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "hey, budy", "status": http.StatusOK})
})
router.GET("/moreJSON", func(c *gin.Context) {
// 注意:这里定义了tag指示在json中显示的是user不是User
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "TAO"
msg.Message = "hey, budy"
msg.Number = 123
// 下面的在client的显示是"user": "TAO",不是"User": "TAO"
// 所以总体的显示是:{"user": "TAO", "Message": "hey, budy", "Number": 123
c.JSON(http.StatusOK, msg)
})
// 测试发送XML数据
router.GET("/someXML", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"name":"TAO", "message": "hey, budy", "status": http.StatusOK})
})
router.Run(":8888")
}
客户端代码:
func main() {
// 下面测试binding数据
// 首先测试binding-JSON,
// 注意Body中的数据必须是JSON格式
resp,_ = http.Post("http://0.0.0.0:8888/bindJSON", "application/json", strings.NewReader("{\"user\":\"TAO\", \"password\": \"123\"}"))
helpRead(resp)
// 下面测试bind FORM数据
resp,_ = http.Post("http://0.0.0.0:8888/bindForm", "application/x-www-form-urlencoded", strings.NewReader("user=TAO&password=123"))
helpRead(resp)
// 下面测试接收JSON和XML数据
resp,_ = http.Get("http://0.0.0.0:8888/someJSON")
helpRead(resp)
resp,_ = http.Get("http://0.0.0.0:8888/moreJSON")
helpRead(resp)
resp,_ = http.Get("http://0.0.0.0:8888/someXML")
helpRead(resp)
}
客户端发送请求, 在服务端可以直接使用c.BindJSON绑定到Json结构体上. 或者使用BindWith函数也可以, 但是需要指定绑定的数据类型, 例如JSON, XML, HTML等. Bind*函数的本质是读取request中的body数据, 拿BindJSON为例, 其核心代码是:
func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
// 核心代码: decode请求的body到obj中
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(obj); err != nil {
return err
}
return validate(obj)
}
<5> router group
router group是为了方便前缀相同的URL的管理, 其基本用法如下.
首先看服务端代码:
// router GROUP - GET测试
func func10(c *gin.Context) {
c.String(http.StatusOK, "test10 OK")
}
func func11(c *gin.Context) {
c.String(http.StatusOK, "test11 OK")
}
// router GROUP - POST测试
func func12(c *gin.Context) {
c.String(http.StatusOK, "test12 OK")
}
func func13(c *gin.Context) {
c.String(http.StatusOK, "test13 OK")
}
func main(){
router := gin.Default()
// router Group是为了将一些前缀相同的URL请求放在一起管理
group1 := router.Group("/g1")
group1.GET("/read1", func10)
group1.GET("/read2", func11)
group2 := router.Group("/g2")
group2.POST("/write1", func12)
group2.POST("/write2", func13)
router.Run(":8888")
}
客户端测试代码:
func main() {
// 下面测试router 的GROUP
resp,_ = http.Get("http://0.0.0.0:8888/g1/read1")
helpRead(resp)
resp,_ = http.Get("http://0.0.0.0:8888/g1/read2")
helpRead(resp)
resp,_ = http.Post("http://0.0.0.0:8888/g2/write1", "", strings.NewReader(""))
helpRead(resp)
resp,_ = http.Post("http://0.0.0.0:8888/g2/write2", "", strings.NewReader(""))
helpRead(resp)
}
在服务端代码中, 首先创建了一个组group1 := router.Group(“/g1”), 并在这个组下注册了两个服务, group1.GET(“/read1”, func10) 和group1.GET(“/read2”, func11), 那么当使用http://0.0.0.0:8888/g1/read1和http://0.0.0.0:8888/g1/read2访问时, 是可以路由 到上面注册的位置的. 同理对于group2 := router.Group(“/g2”)也是一样的.
<6> 静态文件服务
可以向客户端展示本地的一些文件信息, 例如显示某路径下地文件. 服务端代码是:
func main(){
router := gin.Default()
// 下面测试静态文件服务
// 显示当前文件夹下的所有文件/或者指定文件
router.StaticFS("/showDir", http.Dir("."))
router.Static("/files", "/bin")
router.StaticFile("/image", "./assets/1.png")
router.Run(":8888")
}
首先你需要在服务器的路径下创建一个assert文件夹, 并且放入1.png文件. 如果已经存在, 请忽略.
测试代码: 请在浏览器中输入0.0.0.0:8888/showDir, 显示的是服务器当前路径下地文件信息:
输入0.0.0.0:8888/files, 显示的是/bin目录下地文件信息:
输入0.0.0.0:8888/image, 显示的是服务器下地./assets/1.png图片:
<7> 加载模板templates
gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据.
看服务端代码
func main(){
router := gin.Default()
// 下面测试加载HTML: LoadHTMLTemplates
// 加载templates文件夹下所有的文件
router.LoadHTMLGlob("templates/*")
// 或者使用这种方法加载也是OK的: router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
router.GET("/index", func(c *gin.Context) {
// 注意下面将gin.H参数传入index.tmpl中!也就是使用的是index.tmpl模板
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "GIN: 测试加载HTML模板",
})
})
router.Run(":8888")
}
客户端测试代码是:
func main() {
// 测试加载HTML模板
resp,_ = http.Get("http://0.0.0.0:8888/index")
helpRead(resp)
}
在服务端, 我们需要加载需要的templates, 这里有两种方法: 第一种使用LoadHTMLGlob加载所有的正则匹配的模板, 本例中使用的是*, 即匹配所有文件, 所以加载的是 templates文件夹下所有的模板. 第二种使用LoadHTMLFiles加载指定文件. 在本例服务器路径下有一个templates目录, 下面有一个index.tmpl模板, 模板的 内容是:
<html>
<h1>
{ { .title } }
</h1>
</html>
当客户端请求/index时, 服务器使用这个模板, 并填充相应的参数, 此处参数只有title, 然后将HTML数据返回给客户端.
你也可以在浏览器请求0.0.0.0:8888/index, 效果如下图所示:
<8> 重定向
重定向相对比较简单, 服务端代码是:
func main(){
router := gin.Default()
// 下面测试重定向
router.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://shanshanpt.github.io/")
})
router.Run(":8888")
}
客户端测试代码是:
func main() {
// 下面测试重定向
resp,_ = http.Get("http://0.0.0.0:8888/redirect")
helpRead(resp)
}
当我们请求http://0.0.0.0:8888/redirect的时候, 会重定向到http://shanshanpt.github.io/这个站点.
<9> 使用middleware
这里使用了两个例子, 一个是logger, 另一个是BasiAuth, 具体看服务器代码:
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 设置example变量到Context的Key中,通过Get等函数可以取得
c.Set("example", "12345")
// 发送request之前
c.Next()
// 发送request之后
latency := time.Since(t)
log.Print(latency)
// 这个c.Write是ResponseWriter,我们可以获得状态等信息
status := c.Writer.Status()
log.Println(status)
}
}
func main(){
router := gin.Default()
// 1
router.Use(Logger())
router.GET("/logger", func(c *gin.Context) {
example := c.MustGet("example").(string)
log.Println(example)
})
// 2
// 下面测试BasicAuth()中间件登录认证
//
var secrets = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
}
// Group using gin.BasicAuth() middleware
// gin.Accounts is a shortcut for map[string]string
authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
// 请求URL: 0.0.0.0:8888/admin/secrets
authorized.GET("/secrets", func(c *gin.Context) {
// get user, it was set by the BasicAuth middleware
user := c.MustGet(gin.AuthUserKey).(string)
if secret, ok := secrets[user]; ok {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})
router.Run(":8888")
}
客户端测试代码是:
func main() {
// 下面测试使用中间件
resp,_ = http.Get("http://0.0.0.0:8888/logger")
helpRead(resp)
// 测试验证权限中间件BasicAuth
resp,_ = http.Get("http://0.0.0.0:8888/admin/secrets")
helpRead(resp)
}
服务端使用Use方法导入middleware, 当请求/logger来到的时候, 会执行Logger(), 并且我们知道在GET注册的时候, 同时注册了匿名函数, 所有请看Logger函数中存在一个c.Next()的用法, 它是取出所有的注册的函数都执行一遍, 然后再回到本函数中, 所以, 本例中相当于是先执行了 c.Next()即注册的匿名函数, 然后回到本函数继续执行. 所以本例的Print的输出顺序是:
log.Println(example)
log.Print(latency)
log.Println(status)
如果将c.Next()放在log.Print(latency)后面, 那么log.Println(example)和log.Print(latency)执行的顺序就调换了. 所以一切都取决于c.Next()执行的位置. c.Next()的核心代码如下:
// Next should be used only in the middlewares.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func (c *Context) Next() {
c.index++
s := int8(len(c.handlers))
for ; c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
它其实是执行了后面所有的handlers.
关于使用gin.BasicAuth() middleware, 可以直接使用一个router group进行处理, 本质和logger一样.
<10> 绑定http server
之前所有的测试中, 我们都是使用router.Run(":8888")
开始执行监听, 其实还有两种方法:
// 方法二
http.ListenAndServe(":8888", router)
// 方法三:
server := &http.Server{
Addr: ":8888",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
server.ListenAndServe()
至此, gin最基本的一些应用都整理完了
Go语言WEB框架(Gin)详解
在 Go语言开发的 Web 框架中,有两款著名 Web 框架分别是 Martini 和 Gin,两款 Web 框架相比较的话,Gin 自己说它比 Martini 要强很多。Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。总之在 Go语言开发领域是一款值得好好研究的 Web 框架,开源网址:https://github.com/gin-gonic/gin
首先下载安装 gin 包:
go get -u github.com/gin-gonic/gin
一个简单的例子:package main import "github.com/gin-gonic/gin" func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/ping",func(c *gin.Context) { //输出json结果给调用方 c.JSON(200,gin.H{ "message": "pong",}) }) r.Run() // listen and serve on 0.0.0.0:8080 }编译运行程序,打开浏览器,访问
http://localhost:8080/ping
页面显示:{"message":"pong"}
gin 的功能不只是简单输出 Json 数据。它是一个轻量级的 WEB 框架,支持 RestFull 风格 API,支持 GET,POST,PUT,PATCH,DELETE,OPTIONS 等 http 方法,支持文件上传,分组路由,Multipart/Urlencoded FORM,以及支持 JsonP,参数处理等等功能,这些都和 WEB 紧密相关,通过提供这些功能,使开发人员更方便地处理 WEB 业务。Gin 实际应用
接下来使用 Gin 作为框架来搭建一个拥有静态资源站点,动态 WEB 站点,以及 RESTFull API 接口站点(可专门作为手机 APP 应用提供服务使用)组成的,亦可根据情况分拆这套系统,每种功能独立出来单独提供服务。下面按照一套系统但采用分站点来说明,首先是整个系统的目录结构,website 目录下面 static 是资源类文件,为静态资源站点专用;photo 目录是 UGC 上传图片目录,tpl 是动态站点的模板。
当然这个目录结构是一种约定,可以根据情况来修改。整个项目已经开源,可以访问来详细了解:https://github.com/ffhelicopter/tmm具体每个站点的功能怎么实现呢?请看下面有关每个功能的讲述:
静态资源站点
一般网站开发中,我们会考虑把 js,css,以及资源图片放在一起,作为静态站点部署在 CDN,提升响应速度。采用 Gin 实现起来非常简单,当然也可以使用 net/http 包轻松实现,但使用 Gin 会更方便。不管怎么样,使用 Go 开发,我们可以不用花太多时间在 WEB 服务环境搭建上,程序启动就直接可以提供 WEB 服务了。
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // 静态资源加载,本例为css,js以及资源图片 router.StaticFS("/public",http.Dir("D:/goproject/src/github.com/ffhelicopter/tmm/website/static")) router.StaticFile("/favicon.ico","./resources/favicon.ico") // Listen and serve on 0.0.0.0:80 router.Run(":80") }首先需要是生成一个 Engine,这是 gin 的核心,默认带有 Logger 和 Recovery 两个中间件。
router := gin.Default()
StaticFile 是加载单个文件,而 StaticFS 是加载一个完整的目录资源:
func (group *RouterGroup) StaticFile(relativePath,filepath string) IRoutes
func (group *RouterGroup) StaticFS(relativePath string,fs http.FileSystem) IRoutes
访问
http://localhost/public/images/logo.jpg
图片加载正常。每次请求响应都会在服务端有日志产生,包括响应时间,加载资源名称,响应状态值等等。动态站点
如果需要动态交互的功能,比如发一段文字+图片上传。由于这些功能出来前端页面外,还需要服务端程序一起来实现,而且迭代需要经常需要修改代码和模板,所以把这些统一放在一个大目录下,姑且称动态站点。tpl 是动态站点所有模板的根目录,这些模板可调用静态资源站点的 css,图片等;photo 是图片上传后存放的目录。
package main import ( "context" "log" "net/http" "os" "os/signal" "time" "github.com/ffhelicopter/tmm/handler" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // 静态资源加载,本例为css,"./resources/favicon.ico") // 导入所有模板,多级目录结构需要这样写 router.LoadHTMLGlob("website/tpl/*/*") // website分组 v := router.Group("/") { v.GET("/index.html",handler.IndexHandler) v.GET("/add.html",handler.AddHandler) v.POST("/postme.html",handler.PostmeHandler) } // router.Run(":80") // 这样写就可以了,下面所有代码(go1.8+)是为了优雅处理重启等动作。 srv := &http.Server{ Addr: ":80",Handler: router,ReadTimeout: 30 * time.Second,WriteTimeout: 30 * time.Second,} go func() { // 监听请求 if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n",err) } }() // 优雅Shutdown(或重启)服务 quit := make(chan os.Signal) signal.Notify(quit,os.Interrupt) // syscall.SIGKILL <-quit log.Println("Shutdown Server ...") ctx,cancel := context.WithTimeout(context.Background(),5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:",err) } select { case <-ctx.Done(): } log.Println("Server exiting") }在动态站点实现中,引入 WEB 分组以及优雅重启这两个功能。WEB 分组功能可以通过不同的入口根路径来区别不同的模块,这里我们可以访问:
http://localhost/index.html
。如果新增一个分组,比如:v := router.Group("/login")
我们可以访问:http://localhost/login/xxxx
,xxx 是我们在 v.GET 方法或 v.POST 方法中的路径。
// 导入所有模板,多级目录结构需要这样写
router.LoadHTMLGlob("website/tpl/*/*")
// website分组
v := router.Group("/")
{
v.GET("/index.html",handler.IndexHandler)
v.GET("/add.html",handler.AddHandler)
v.POST("/postme.html",handler.PostmeHandler)
}
router.LoadHTMLGlob("website/tpl/*/*")
比如 v.GET("/index.html",handler.IndexHandler),通过访问http://localhost/index.html
这个 URL,实际由 handler.IndexHandler 来处理。而在 tmm 目录下的 handler 存放了 package handler 文件。在包里定义了 IndexHandler 函数,它使用了 index.html 模板。
func IndexHandler(c *gin.Context) {
c.HTML(http.StatusOK,"index.html",gin.H{
"Title": "作品欣赏",
})
}
<!DOCTYPE html> <html> <head> {{template "header" .}} </head> <body> <!--导航--> <divhttps://www.jb51.cc/tag/Feed/" target="_blank">Feeds"> <div> <a href="https://www.jb51.cc/index.tml">欣赏</a> <a href="https://www.jb51.cc/add.html"> <svgaria-hidden="true"> <use xlink:href="https://www.jb51.cc#icon-add"></use> </svg> 发布 </a> </div> <input type="hidden" id="showmore" value="{$showmore}"> <input type="hidden" id="page" value="{$page}"> <!--</div>--> </div> <script type="text/javascript"> var done = true; $(window).scroll(function(){ var scrollTop = $(window).scrollTop(); var scrollHeight = $(document).height(); var windowHeight = $(window).height(); var showmore = $("#showmore").val(); if(scrollTop + windowHeight + 300 >= scrollHeight && showmore == 1 && done){ var page = $("#page").val(); done = false; $.get("{:U('Product/listsAjax')}",{ page : page },function(json) { if (json.rs != "") { $(".Feeds").append(json.rs); $("#showmore").val(json.showmore); $("#page").val(json.page); done = true; } },'json'); } }); </script> <script src="//at.alicdn.com/t/font_ttszo9rnm0wwmi.js"></script> </body> </html>在 index.html 模板中,通过 {{template "header" .}} 语句,嵌套了 header.html 模板。
header.html 模板:
{{ define "header" }} <Meta charset="UTF-8"> <Meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"> <Meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <Meta name="format-detection" content="telephone=no,email=no"> <title>{{ .Title }}</title> <link rel="stylesheet" href="https://www.jb51.cc/public/css/common.css"> <script src="/public/lib/jquery-3.1.1.min.js"></script> <script src="/public/lib/jquery.cookie.js"></script> <link href="https://www.jb51.cc/public/css/font-awesome.css?v=4.4.0" rel="stylesheet"> {{ end }}{{ define "header" }} 让我们在模板嵌套时直接使用 header 名字,而在 index.html 中的 {{template "header" .}} 注意“.”,可以使参数嵌套传递,否则不能传递,比如这里的 Title。
现在我们访问
http://localhost/index.html
,可以看到浏览器显示 Title 是“作品欣赏”,这个 Title 是通过 IndexHandler 来指定的。接下来点击“发布”按钮,我们进入发布页面,上传图片,点击“完成”提交,会提示我们成功上传图片。可以在 photo 目录中看到刚才上传的图片。
有关 IPFS: IPFS 本质上是一种内容可寻址、版本化、点对点超媒体的分布式存储、传输协议,目标是补充甚至取代过去 20 年里使用的超文本媒体传输协议(HTTP),希望构建更快、更安全、更自由的互联网时代。注意:由于在本人在发布到 github 的代码中,在处理图片上传的代码中,除了服务器存储外,还实现了 IPFS 发布存储,如果不需要 IPFS,请注释相关代码。
IPFS 不算严格意义上区块链项目,是一个去中心化存储解决方案,但有些区块链项目通过它来做存储。
IPFS 项目有在 github 上开源,Go语言实现哦,可以关注并了解。
优雅重启在迭代中有较好的实际意义,每次版本发布,如果直接停服务在部署重启,对业务还是有蛮大的影响,而通过优雅重启,这方面的体验可以做得更好些。这里 ctrl + c 后过 5 秒服务停止。
中间件的使用,在 API 中可能使用限流,身份验证等
Go语言中 net/http 设计的一大特点就是特别容易构建中间件。gin 也提供了类似的中间件。需要注意的是在 gin 里面中间件只对注册过的路由函数起作用。
而对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。大致分为全局中间件,单个路由中间件和分组中间件。
即使是全局中间件,其使用前的代码不受影响。也可在 handler 中局部使用,具体见 api.GetUser。
在高并发场景中,有时候需要用到限流降速的功能,这里引入一个限流中间件。有关限流方法常见有两种,具体可自行研究,这里只讲使用。
导入
import "github.com/didip/tollbooth/limiter"
包,在上面代码基础上增加如下语句:
//rate-limit 限流中间件
lmt := tollbooth.NewLimiter(1,nil)
lmt.SetMessage("服务繁忙,请稍后再试...")
v.GET("/index.html",LimitHandler(lmt),handler.IndexHandler)
当 F5 刷新刷新http://localhost/index.html
页面时,浏览器会显示:服务繁忙,请稍后再试...限流策略也可以为 IP:
tollbooth.LimitByKeys(lmt,[]string{"127.0.0.1","/"})
更多限流策略的配置,可以进一步github.com/didip/tollbooth/limiter
了解。RestFull API 接口
前面说了在 gin 里面可以采用分组来组织访问 URL,这里 RestFull API 需要给出不同的访问 URL 来和动态站点区分,所以新建了一个分组 v1。在浏览器中访问
http://localhost/v1/user/1100000/
这里对 v1.GET("/user/:id/*action",api.GetUser) 进行了限流控制,所以如果频繁访问上面地址也将会有限制,这在 API 接口中非常有作用。通过 api 这个包,来实现所有有关 API 的代码。在 GetUser 函数中,通过读取 MysqL 数据库,查找到对应 userid 的用户信息,并通过 Json 格式返回给 client。
在 api.GetUser 中,设置了一个局部中间件:
//CORS 局部CORS,可在路由中设置全局的CORS
c.Writer.Header().Add("Access-Control-Allow-Origin","*")
完整 mian.go 代码:
package main import ( "context" "log" "net/http" "os" "os/signal" "time" "github.com/didip/tollbooth" "github.com/didip/tollbooth/limiter" "github.com/ffhelicopter/tmm/api" "github.com/ffhelicopter/tmm/handler" "github.com/gin-gonic/gin" ) // 定义全局的CORS中间件 func Cors() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Add("Access-Control-Allow-Origin","*") c.Next() } } func LimitHandler(lmt *limiter.Limiter) gin.HandlerFunc { return func(c *gin.Context) { httpError := tollbooth.LimitByRequest(lmt,c.Writer,c.Request) if httpError != nil { c.Data(httpError.StatusCode,lmt.GetMessageContentType(),[]byte(httpError.Message)) c.Abort() } else { c.Next() } } } func main() { gin.SetMode(gin.ReleaseMode) router := gin.Default() // 静态资源加载,本例为css,"./resources/favicon.ico") // 导入所有模板,多级目录结构需要这样写 router.LoadHTMLGlob("website/tpl/*/*") // 也可以根据handler,实时导入模板。 // website分组 v := router.Group("/") { v.GET("/index.html",handler.PostmeHandler) } // 中间件 golang的net/http设计的一大特点就是特别容易构建中间件。 // gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。 // 对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。 // 大致分为全局中间件,单个路由中间件和群组中间件。 // 使用全局CORS中间件。 // router.Use(Cors()) // 即使是全局中间件,在use前的代码不受影响 // 也可在handler中局部使用,见api.GetUser //rate-limit 中间件 lmt := tollbooth.NewLimiter(1,nil) lmt.SetMessage("服务繁忙,请稍后再试...") // API分组(RESTFULL)以及版本控制 v1 := router.Group("/v1") { // 下面是群组中间的用法 // v1.Use(Cors()) // 单个中间件的用法 // v1.GET("/user/:id/*action",Cors(),api.GetUser) // rate-limit v1.GET("/user/:id/*action",api.GetUser) //v1.GET("/user/:id/*action",api.GetUser) // AJAX OPTIONS ,下面是有关OPTIONS用法的示例 // v1.OPTIONS("/users",OptionsUser) // POST // v1.OPTIONS("/users/:id",OptionsUser) // PUT,DELETE } srv := &http.Server{ Addr: ":80",} go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n",err) } }() // 优雅Shutdown(或重启)服务 // 5秒后优雅Shutdown服务 quit := make(chan os.Signal) signal.Notify(quit,os.Interrupt) //syscall.SIGKILL <-quit log.Println("Shutdown Server ...") ctx,err) } select { case <-ctx.Done(): } log.Println("Server exiting") }
关于6个最好的Go语言Web框架和go语言web框架推荐的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于C语言WEB框架小进展-静态文件返回、go语言web开发框架—gin框架入门、Go语言web框架 gin、Go语言WEB框架(Gin)详解等相关知识的信息别忘了在本站进行查找喔。
本文标签: