Go的Web框架 框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用。
Go 是一门正在快速增长的编程语言,专为构建简单、快速且可靠的软件而设计。 golang提供的net/http库已经很好了,对于http的协议的实现非常好 ,基于此再造框架,也不会是难事,因此生态中出现了很多框架。
1. Beego Beego是一个RESTful HTTP框架,用于快速开发Go应用程序,包括API,Web应用程序和具有集成的Go特定功能(如接口和结构嵌入)的后端服务。
Beego采用Go原生的http请求,goroutine的并发效率应付高并发。
1.1 Bee工具 安装完Beego后,再get下来bee工具。我们使用Bee来帮助我们生成项目结构,运行项目,实现热部署 。使用Bin
目录下的bee工具。
例如bee new xxx
命令,或者是bee run
命令进行新建项目和运行项目。
1.2 项目的组织架构 Bee生成的项目的组织架构,是遵循我们的MVC模式的。
1.3 beego.Run()的流程 我们直接看到main.go
文件里面:
package mainimport ( _ "QianfengBeegoDemo1/routers" "github.com/astaxie/beego" ) func main () { beego.Run() }
然后到我们routers的文件下,执行init方法,会进行http路由的配置 ,下面是默认的路由配置
package routersimport ( "QianfengBeegoDemo1/controllers" "github.com/astaxie/beego" ) func init () { beego.Router("/" , &controllers.MainController{}) }
init
方法分析完毕后,程序会继续往下执行,就到了main函数,在main函数中执行: beego.Run()代码。分析一下Run代码的逻辑,在Run方法内部,主要做了几件事:
解析配置文件,也就是我们的app.conf文件,比如端口,应用名称等信息。
检查是否开启session , 如果开启session, 就会初始化一 个session对象。
是否编译模板,beego框架会在项目启动的时候根据配置把views目录下的所有模板进行预编译,然后存放在map中,这样可以有效的提高模板运行的效率,不需要进行多次编译。
监听服务端口。 根据app.conf文件中的端口配置,启动监听。
func Run (params ...string ) { initBeforeHTTPRun() if len (params) > 0 && params[0 ] != "" { strs := strings.Split(params[0 ], ":" ) if len (strs) > 0 && strs[0 ] != "" { BConfig.Listen.HTTPAddr = strs[0 ] } if len (strs) > 1 && strs[1 ] != "" { BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1 ]) } BConfig.Listen.Domains = params } BeeApp.Run() }
进入到initBeforeHTTPRun
方法,顾名思义是http初始化之前进行运行的函数。初始化了一些钩子hook,有注册的session,模板,默认错误提示的handler
func initBeforeHTTPRun () { AddAPPStartHook( registerMime, registerDefaultErrorHandler, registerSession, registerTemplate, registerAdmin, registerGzip, ) for _, hk := range hooks { if err := hk(); err != nil { panic (err) } } }
1.4 路由routers设置 与Javaweb开发一致,需要指定http路由与对于controller的关系
基础路由: 直接指派某一url对应的请求方法,并在Get/Post方法中编写逻辑
beego.Get("/getUserInfo" , func (context *context.Context) { beego.Info("基础路由get方法 getUserInfo" ) context.Output.Body([]byte ("获取用户信息" )) }) beego.Post("/inputText" , func (context *context.Context) { beego.Info("基础路由post方法 inputText" ) context.Output.Body([]byte ("上传信息" )) }) beego.Delete("/deleteInfo" , func (c *context.Context) { beego.Info("基础路由delete方法 deleteInfo" ) c.ResponseWriter.Write([]byte ("删除用户信息" )) })
固定路由: 会根据浏览器发出的请求,去执行对于controller对应的Get、Post、Delete请求。 例如项目默认的beego.Router("/", &controllers.MainController{})
beego.Router("/" , &controllers.MainController{}) beego.Router("/request" , &controllers.MainController{})
在MainController中:
type MainController struct { beego.Controller } func (c *MainController) Get () { c.Data["Website" ] = "beego.me" c.Data["Email" ] = "astaxie@gmail.com" c.TplName = "index.tpl" } func (c *MainController) Post () { c.Ctx.Output.Body([]byte ("固定路由的 post类型的请求" )) } func (c *MainController) Delete () { c.Ctx.Output.Body([]byte ("固定路由的 delete类型的请求" )) }
正则路由: 固定路由的基础上,支持匹配一定格式的正则表达式。比如:id;username
beego.Router("/getUserInfo/:id:int" , &controllers.RegController{}) beego.Router("/getUserInfo/:name:string" , &controllers.RegController{})
在controller中:正则路由是指可以在进行,自定义正则,fle的路径和后缀切换以及全匹配等操作。
func (c *RegController) Get () { id := c.Ctx.Input.Param(":id" ) c.Ctx.ResponseWriter.Write([]byte (id)) name := c.Ctx.Input.Param(":name" ) c.Ctx.ResponseWriter.Write([]byte (name)) }
beego.Router("/getCustom" , &controllers.MainController{}, "GET:Get" ) beego.Router("/getUserInfoCustom" , &controllers.CustomController{}, "GET:GetUserInfo" ) beego.Router("/inputUserInfoCustom" , &controllers.CustomController{}, "POST:PostUser" )
自定义路由的controller实现
type CustomController struct { beego.Controller } func (c *CustomController) GetUserInfo () { username := c.GetString("username" ) userId := 1 c.Ctx.ResponseWriter.Write([]byte ("username is :" + username + " And userId is " + strconv.Itoa(userId))) } type User struct { userId int `json:"userId"` username string `json:"username"` } func (c *CustomController) PostUser () { user := new (User) data := c.Ctx.Input.RequestBody err := json.Unmarshal(data, user) if err != nil { beego.Error(err.Error()) } c.Ctx.WriteString("username is :" + user.username + " And userId is " + strconv.Itoa(user.userId)) }
1.5 model层的数据库操作
与原生的api类型,在run前执行数据库的init方法 。操作基本与jdbc类似,注册驱动,创建数据库连接等等。
package modelsimport ( "github.com/astaxie/beego" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" ) func init () { orm.RegisterDriver("mysql" , orm.DRMySQL) user := beego.AppConfig.String("mysqluser" ) pwd := beego.AppConfig.String("mysqlupwd" ) host := beego.AppConfig.String("host" ) port := beego.AppConfig.String("port" ) dbname := beego.AppConfig.String("dbname" ) dbConn := user + ":" + pwd + "@tcp(" + host + ":" + port + ")/" + dbname + "?charset=utf8" err := orm.RegisterDataBase("default" , "mysql" , dbConn) if err != nil { beego.Error(err.Error()) return } beego.Info(" connection database success!" ) }
func InsertUser (user User) { neworm := orm.NewOrm() if _, err := neworm.Insert(&user); err != nil { beego.Error(err.Error()) } } neworm.Delete() neworm.Read() neworm.Update()
1.6 session实现和配置
session = true sessionname = "QianfengBeegoDemo1" sessioncmaxlifetime = 1800
func main () { beego.SessionConfig{}.SessionOn = true beego.Run() }
func (c *CustomController) PostUser () { user := new (User) data := c.Ctx.Input.RequestBody err := json.Unmarshal(data, user) if err != nil { beego.Error(err.Error()) } c.SetSession("loginuser" , user.username) c.DelSession("loginuser" ) c.Ctx.WriteString("username is :" + user.username + " And userId is " + strconv.Itoa(user.userId)) }
2. Iris Iris:全宇宙最快的 Go 语言 Web 框架。完备 MVC 支持 ,其它特性如下:
支持定制事件的高可扩展性Websocket API
带有GC, 内存 & redis 提供支持的会话
方便的中间件和插件
完整REST API
能定制HTTP错误
热部署
2.1 处理Http请求和路由组设置(不使用这种)
与beego类似,有基础的路由对应关系,和自定义的 方法类型 + url + 处理逻辑
func main () { app := iris.New() app.Get("/get" , func (context context.Context) { path := context.Path() app.Logger().Info(path) username := context.URLParam("username" ) app.Logger().Info(username) context.JSON(iris.Map{"message" : "hello word" , "requestCode" : 200 }) }) app.Post("/postJson" , func (context context.Context) { path := context.Path() app.Logger().Info(path) person := new (Person) if err := context.ReadJSON(person); err != nil { panic (err.Error()) } context.JSON(iris.Map{"message" : "success" , "Code" : 200 , "data" : person.Username}) }) app.Handle("GET" , "/getHandle" , func (context context.Context) { path := context.Path() app.Logger().Info("ger request url is " + path) context.JSON(iris.Map{"message" : "hello word" , "requestCode" : 200 }) }) app.Handle("POST" , "/postHandle" , func (context context.Context) { path := context.Path() app.Logger().Info("post request url is " + path) person := new (Person) if err := context.ReadJSON(person); err != nil { panic (err.Error()) } app.Logger().Infof("request param is %v" , *person) context.JSON(map [string ]interface {}{ "message" : "success" , "Code" : 200 , "data" : person.Username, }) }) app.Run(iris.Addr(":8080" ), iris.WithoutServerError(iris.ErrServerClosed)) }
与Java的requestmapping一致,iris也有路由分组,使用app.Party
来进行分组派发
app.Party("/users" , func (context context.Context) { context.Next() })
例如localhost:8080/users/
这下面的两个请求
userParty.Get("/register" , func (c context.Context) { app.Logger().Info("用户注册功能" ) c.JSON(map [string ]interface {}{"code" : 200 , "msg" : "success" }) }) userParty.Post("/input" , func (c context.Context) { person := new (Person) if err := c.ReadJSON(person); err != nil { panic (err.Error()) } c.JSON(iris.Map{"message" : "success" , "Code" : 200 , "data" : person.Username}) })
如果在逻辑中再调用context的next,还会再进入到里面一层,这时就会调用路由的Done方法。
usersRouter.Done(func (c context.Context) { c.Application().Logger().Info("到达了最里面一层" + c.Path()) }) usersRouter.Get("/query" , func (c context.Context) { app.Logger().Info("query" ) c.JSON(map [string ]interface {}{"code" : 200 , "msg" : "success" }) c.Next() })
2.2 Iris配置文件 一般的数据库配置、redis缓存配置等等,会通过开始进行配置。
{ "app_name" : "CmsProject" , "port" : "8080" , "static_path" : "/manage/static" , "data_base" : { "drive" : "mysql" , "host" : "127.0.0.1" , "port" : "3306" , "user" : "root" , "pwd" : "yu271400" , "database" : "qfCms" }, "redis" : { "net_work" : "tcp" , "addr" : "127.0.0.1" , "port" : "6379" , "passwword" : "" , "prefix" : "qfCms_" }, "mode" : "dev" }
app.Configure(iris.WithConfiguration(iris.Configuration{ DisableInterruptHandler: false , DisablePathCorrection: false , EnablePathEscape: false , Charset: "utf-8" , }))
app.Configure(iris.WithConfiguration(iris.YAML("src/IrisDemo/3-路由组及Iris配置/configs/iris.yml")))
但我们一般使用json文件(config.json)进行配置,得到一个config对象
func InitConfig () *AppConfig { var config *AppConfig file, err := os.Open("xxx/go/src/irisDemo/14-CmsProject/config.json" ) if err != nil { panic (err.Error()) } decoder := json.NewDecoder(file) err = decoder.Decode(&config) if err != nil { panic (err.Error()) } return config }
例如:拿取配置文件中的数据库配置进行拼接。
initConfig := config.InitConfig() if initConfig == nil { return nil } database := initConfig.DataBase dataSourceName := database.User + ":" + database.Pwd + "@tcp(" + database.Host + ")/" + database.Database + "?charset=utf8"
2.3 MVC包的使用 Iris内置了MVC包,方便我们使用。需要我们注册处理器mvc.New(app).Handle(new(CustomController))
func main () { app := iris.New() mvc.New(app).Handle(new (CustomController)) app.Run(iris.Addr(":8080" ), iris.WithoutServerError(iris.ErrServerClosed)) }
路由对应有三种默认匹配请求、请求类型和请求URL自动匹配处理方法、url对应自定义的controller
type CustomController struct {}func (cc *CustomController) Get () mvc .Result { return mvc.Response{ ContentType:"text/html" ,} } func (cc *CustomController) GetInfo () mvc .Result { return mvc.Response{ ContentType:"text/html" ,} } func (m *CustomController) BeforeActivation (a mvc.BeforeActivation) { a.Handle("GET" ,"/users/info" ,"QueryInfo" ) } func (m *CustomController) QueryInfo () mvc .Result { return mvc.Response{} } type UserController struct {}func (m *UserController) BeforeActivation (a mvc.BeforeActivation) { a.Handle("GET" ,"/users/info" ,"QueryInfo" ) } func (m *UserController) QueryInfo () mvc .Result { return mvc.Response{} }
配置路由组,分组后我们一般使用请求类型和请求URL自动匹配处理方法
func main () { app := iris.New() admin := mvc.New(app.Party("/admin" )) admin.Handle(new (controller.AdminController)) app.Run(iris.Addr(":8080" ), iris.WithoutServerError(iris.ErrServerClosed)) } type AdminController struct { Ctx iris.Context Service service.AdminService Session *sessions.Session } func (ac *AdminController) GetCount () mvc .Result { count, err := ac.Service.GetAdminCount() if err != nil { return mvc.Response{ Object: map [string ]interface {}{ "status" : utils.RECODE_FAIL, "message" : utils.Recode2Text(utils.RESPMSG_ERRORADMINCOUNT), "count" : 0 , }, } } return mvc.Response{ Object: map [string ]interface {}{ "status" : utils.RECODE_OK, "count" : count, }, } }
2.4 session的使用(redis缓存) 与前面大同小异,就不再赘述:一般使用redis缓存来配置session
sessManager := sessions.New(sessions.Config{ Cookie: "sessioncookie" , Expires: 24 * time.Hour, }) redis := datasource.NewRedis() sessManager.UseDatabase(redis) ac.Session.Set(ADMIN, admin.AdminId) userByte := ac.Session.Get(ADMIN) adminId, err := ac.Session.GetInt64(ADMIN) ac.Session.Delete(ADMIN);
2.5 redis配置和使用 从配置文件中,获取配置生成redis实例。在需要时调用,例如设置session数据库时,调用redis := datasource.NewRedis()
redis := datasource.NewRedis() sessManager.UseDatabase(redis) func NewRedis () *redis .Database { var database *redis.Database cmsConfig := config.InitConfig() if cmsConfig != nil { iris.New().Logger().Info(" hello " ) rd := cmsConfig.Redis iris.New().Logger().Info(rd) database = redis.New(service.Config{ Network: rd.NetWork, Addr: rd.Addr + ":" + rd.Port, Password: rd.Password, Database: "" , MaxIdle: 0 , MaxActive: 10 , IdleTimeout: service.DefaultRedisIdleTimeout, Prefix: rd.Prefix, }) } else { iris.New().Logger().Info(" hello error " ) } return database }
2.6 xorm数据库 一般我们进行了数据库与实体对象model的映射关系 ,类似于springdata jpa
engine := datasource.NewMysqlEngine() func NewMysqlEngine () *xorm .Engine { initConfig := config.InitConfig() if initConfig == nil { return nil } database := initConfig.DataBase dataSourceName := database.User + ":" + database.Pwd + "@tcp(" + database.Host + ")/" + database.Database + "?charset=utf8" engine, err := xorm.NewEngine(database.Drive, dataSourceName) iris.New().Logger().Info(database) err = engine.Sync2(new ( model.Permission), new (model.City), new (model.Admin), new (model.User), new (model.UserOrder), new (model.Address), new (model.Shop), new (model.OrderStatus), new (model.FoodCategory)) if err != nil { panic (err.Error()) } engine.ShowSQL(true ) engine.SetMaxOpenConns(10 ) return engine }
不过我们实体需要通过json来指定关系,通过xorm来设置字段属性(主键、数据类型等)
type Admin struct { AdminId int64 `xorm:"pk autoincr" json:"id"` AdminName string `xorm:"varchar(32)" json:"admin_name"` CreateTime time.Time `xorm:"DateTime" json:"create_time"` Status int64 `xorm:"default 0" json:"status"` Avatar string `xorm:"varchar(255)" json:"avatar"` Pwd string `xorm:"varchar(255)" json:"pwd"` CityName string `xorm:"varchar(12)" json:"city_name"` CityId int64 `xorm:"index" json:"city_id"` City *City `xorm:"- <- ->"` }
var person PersonTableengine.Id(1 ).Get(&person) engine.Id(core.PK(1 ,"davie" ).Get(&user) engine.Where(" person_age = ? and person_sex = ?" , 26 , 2 ).Get(&person1) err = engine.Where(" person_age = ? " , 26 ).And("person_sex = ? " , 2 ).Find(&persons) var personsNative []PersonTableerr = engine.SQL(" select * from person_table where person_name like 't%' " ).Find(&personsNative) personInsert := PersonTable{ PersonName: "Hello" , PersonAge: 18 , PersonSex: 1 , } rowNum, err := engine.Insert(&personInsert) fmt.Println(rowNum) fmt.Println() rowNum, err := engine.Delete(&personInsert) fmt.Println(rowNum) fmt.Println() rowNum, err = engine.Id(7 ).Update(&personInsert) fmt.Println(rowNum) fmt.Println() count, err := engine.Count(new (PersonTable)) fmt.Println("PersonTable表总记录条数:" , count) personsArray := []PersonTable{ PersonTable{ PersonName: "Jack" , PersonAge: 28 , PersonSex: 1 , }, PersonTable{ PersonName: "Mali" , PersonAge: 28 , PersonSex: 1 , }, PersonTable{ PersonName: "Ruby" , PersonAge: 28 , PersonSex: 1 , }, } session := engine.NewSession() session.Begin() for i := 0 ; i < len (personsArray); i++ { _, err = session.Insert(personsArray[i]) if err != nil { session.Rollback() session.Close() } } err = session.Commit() session.Close() if err != nil { panic (err.Error()) }
3. Gin框架 Gin:Go 语言编写的 Web 框架,以更好的性能实现类似 Martini 框架的 API。
Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等特点。
3.1 请求处理和路由分组 与前两者大同小异,这里不再赘述:
engine.Handle("GET" , "/helloHandle" , func (context *gin.Context) { path := context.FullPath() fmt.Println(path) name := context.DefaultQuery("name" , "hello" ) fmt.Println(name) context.Writer.Write([]byte ("hello, " + name)) }) engine.Handle("POST" , "/loginHandle" , func (context *gin.Context) { path := context.FullPath() fmt.Println(path) name := context.PostForm("username" ) pwd := context.PostForm("password" ) fmt.Println(pwd) context.Writer.Write([]byte ("hello, " + name)) }) engine.GET("/hello" , func (context *gin.Context) { fmt.Println("请求路径:" , context.FullPath()) username := context.Query("username" ) fmt.Println(username) context.Writer.Write([]byte ("hello gin\n" )) }) engine.POST("/login" , func (context *gin.Context) { fmt.Println("请求路径:" , context.FullPath()) username := context.PostForm("username" ) fmt.Println(username) pwd, exist := context.GetPostForm("password" ) if exist { fmt.Println(pwd) } context.Writer.Write([]byte ("hello gin\n" )) })
接受请求参数和json数据,返回json数据、路由分组
type User struct { Username string `form:"username"` Password string `form:"password"` } type Result struct { Code int Msg string } func loginHandle (context *gin.Context) { fmt.Println("请求路径:" , context.FullPath()) var user User err := context.ShouldBindJSON(&user) if err != nil { log.Fatal(err.Error()) return } context.JSON(200 , map [string ]interface {} { "code" : 1 , "message" : "success" , "data" : user, }) context.JSON(200 , &Result{ Code: 200 , Msg: "success" , }) } engine.GET("/helloInfo" , func (context *gin.Context) { fmt.Println("请求路径:" , context.FullPath()) var user User err := context.ShouldBindQuery(&user) if err != nil { log.Fatal(err.Error()) return } context.Writer.Write([]byte ("hello gin\n" )) }) engine.POST("/loginInfo" , loginHandle) userGroup := engine.Group("/user" ) userGroup.POST("/loginGroup" , loginHandle)
3.2 中间件编写使用 在实际的业务开发和处理中,会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度的系统支持。鉴权认证、权限管理、安全检查、日志记录等这些保障和支持系统业务属于全系统的业务,和具体的系统业务没有关联,对于系统中的所有业务都适用,类似于我们spring中的aop
在业务开发过程中,将通用业务单独抽离并进行开发 ,然后以插件化的形式进行对接。 中间件其位于服务器和实际业务处理程序之间。其含义就是相当于在请求和具体的业务逻辑处理之间增加某些操作,这种以额外添加的方式不会影响编码效率,也不会侵入到框架中。中间件的位置和角色示意图如下图所示:
我们知道我们使用engine := gin.Default()
来创建Gin引擎,实际上默认自带两个中间件,打印日志和错误提示。
engine.Use(Logger(), Recovery()) func (engine *Engine) Use (middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine }
middleware就是中间件,其类型是HandlerFunc
,这个中间件的类型是HandlerFunc其实就是一个函数,绑定了context。
type HandlerFunc func (*Context) func Logger () HandlerFunc { return LoggerWithConfig(LoggerConfig{}) }
根据上文的介绍,可以自己定义实现一个名为RequestInfos的中间件,在该中间件中打印请求的path和method。
func RequestInfos () gin .HandlerFunc { return func (context *gin.Context) { path := context.FullPath() method := context.Request.Method fmt.Println("path: " + path + "\nmethod: " + method) context.Next() } } func main () { engine := gin.Default() engine.Use(RequestInfos()) engine.GET("/helloMiddleware" , func (context *gin.Context) { var user User err := context.ShouldBindQuery(&user) if err != nil { log.Fatal(err.Error()) return } context.JSON(200 , map [string ]interface {} { "code" : 200 , "data" : map [string ]interface {} { "username" : user.Username, "msg" : "success" , }, }) }) if err := engine.Run(":8080" ); err != nil { log.Fatal(err.Error()) } }
还可以为某一特定请求配置中间件
engine.GET("/queryMiddleware" , RequestInfos2(), func (context *gin.Context) { var user User err := context.ShouldBindQuery(&user) if err != nil { log.Fatal(err.Error()) return } context.JSON(200 , map [string ]interface {} { "code" : 200 , "data" : map [string ]interface {} { "username" : user.Username, "msg" : "success" , }, }) })
进入到后面一层,即业务逻辑层进行处理请求,然后还可以等待处理完后,再后续执行context.Next
后面的代码。
例如:去执行业务处理逻辑,即后面handle中的处理请求,然后再回中间件中打印状态码。
func RequestInfos2 () gin .HandlerFunc { return func (context *gin.Context) { path := context.FullPath() method := context.Request.Method fmt.Println("path: " + path + "\nmethod: " + method) context.Next() var user User if err := context.ShouldBindQuery(&user); err != nil { log.Fatal(err.Error()) } fmt.Printf("状态码为:%v\n" , context.Writer.Status()) } }
3.3 数据库操作 内部没有集成数据库的框架,所以就使用原生的database/sql
下面的数据库操作。
也可以使用我们的xorm来进行数据库操作,操作与Iris上述一致。
3.4 配置文件 配置文件的解析与Iris一致,独立一个Config文件,里面有initConfig方法,使用文件I/O解析路径下的json文件
3.5 跨域访问 类似与SpringBoot中的@CrossOrigin
注解,实现跨越功能。例如,在gin接口项目中,前端使用nodejs开发,运行在8080端口,我们访问的应用首页,是: http://localhost:8080。后端服务器的监听端口为8090。一个端口数8080,一个是8090,两者端口不同,因此按照规定,发生了跨域访问。
如上文所述,前端vue开发的功能,使用axios发送POST登录请求。 在请求时发生了跨域访问,为了安全起见,首先发起一个请求测试一下此次访问是否安全,这种测试的请求类型为OPTIONS,又称之为options嗅探, 同时在header中会带上origin,用来判断是否有跨域请求权限。然后服务器相应Access-Control-Allow-Origin 的值,该值会与浏览器的origin值进行匹配 ,如果能够匹配通过,则表示有跨域访问的权限。跨域访问权限检查通过,会正式发送POST请求。
例如:前端端口发送一个8090服务器端的请求,是跨域的,所以先发送一个options请求,看响应结果是否与origin中的目标值一致。
我们采取的方案是在服务端,编写跨域访问的中间件,并设置生效,从而解决跨域访问的问题。跨域访问的中间件编程实现如下所示:
func Cors () gin .HandlerFunc { return func (context *gin.Context) { method := context.Request.Method origin := context.Request.Header.Get("Origin" ) var headerKeys []string for key, _ := range context.Request.Header { headerKeys = append (headerKeys, key) } headerStr := strings.Join(headerKeys, "," ) if headerStr != "" { fmt.Sprintf("access-control-allow-origin、access-control-allow-headers, %s" , headerStr) } else { headerStr = "access-control-allow-origin、access-control-allow-headers" } if origin != "" { context.Writer.Header().Set("Access-Control-Allow-Origin" , "*" ) context.Header("Access-Control-Allow-Origin" , "*" ) context.Header("Access-Control-Allow-Methods" , "POST, GET, OPTIONS, PUT, DELETE, UPDATE" ) context.Header("Access-Control-Allow-Headers" , "Authorization, Content-Length" ) context.Header("Access-Control-Expose-Headers" , "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers" ) context.Header("Access-Control-Max-Age" , "172800" ) context.Header("Access-Control-Allow-Credentials" ,"false" ) context.Set("content-type" , "application/json" ) } if method == "OPTIONS" { context.JSON(http.StatusOK, "Options Request!" ) } context.Next() } }
下图中我们可以看到,拦截到了option跨域请求,设置了对应的响应头header字段 ,例如Access-Control-Allow-Origin,Access-Control-Allow-Methods。
允许跨域访问,然后去执行我们的原的post请求