Golang 跨域
跨域指的是浏览器不能执行其他网站或域名下的脚本。之所以形成跨域,是因为浏览器的 同源策略
造成的,是浏览器对javascript程序做的安全限制,现在所有支持 JavaScript
的浏览器都会使用这个策略。
在实际应用中会遇到需要跨域的场景,比如前后端分离,前后端不在同域(这里的同域指的是同一协议,同一域名,同一端口),那么,它们之间相互通信如何解决呢?
跨域解决有以下几种方法:
jsonp跨域
这里 jsonp
跨域其实是利用iframe、img、srcipt,link标签的src或href属性来实现的,这些标签都可以发送一个get请求资源,src 和href 并没有受同源策略的限制。
这里我们拿懒人教程示例
JSONP 实例 $.getJSON("https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=?", function(data) { var html = '
- ';
for(var i = 0; i < data.length; i++)
{
html += '
- ' + data[i] + ' '; } html += '
jsonp主要站在前端的角度去解决问题,这种方式有一定的局限性,就是仅适用get请求。
nginx代理跨域
1、nginx配置解决iconfont跨域
众所周知js、css、img等常用资源不受浏览器同源策略限制,但一些特殊资源如iconfont字体文件(eot|otf|ttf|woff|svg)除外,这里通过修改nginx配置就可以解决。
location / { add_header Access-Control-Allow-Origin *; }
2、nginx 反向代理
同源策略是浏览器的安全策略,不属于http协议一部分,限制的是js脚本。而服务器端调用的http接口,不受同源策略限制,也不存在跨域问题。
实现思路:nginx服务器作为中间代理(或跳转机),实现从域名A访问域名B,像访问同域一样。
示例
server { listen 80; server_name http://domain1; location / { proxy_pass http://domain2:8081/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; } }
nodejs 代理
nodejs实现原理和nginx基本类似。
修改app.js
var express = require('express'); const proxy = require('http-proxy-middleware'); const app = express(); app.set('port', '809'); app.all('*', function (req, res, next) { // 解决跨域问题 res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); if (req.method == "OPTIONS") { res.send(200); } else { next(); } }); var options = { target: 'http://localhost:8090', changeOrigin: true, }; var exampleProxy = proxy(options); app.use('/', exampleProxy); app.listen(app.get('port'), () => { console.log(`server running @${app.get('port')}`); });
如是vue+nodejs环境
通过只修改vue.config.js,不用修改nodejs也可以实现代理跨域。
devServer: { host: '0.0.0.0', port: 8080, disableHostCheck: true, proxy: { '/*': { target: 'https://www.runoob.com', secure: false, changeOrigin: true } } }
cors
跨域资源共享( CORS
) 是一种机制,它使用额外的 HTTP
头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器 不同的域、协议或端口
请求一个资源时,资源会发起一个 跨域 HTTP 请求
。
比如,站点 http://domain-a.com
的某 HTML 页面通过 的 src
请求 http://domain-b.com/image.jpg
。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
前面扯了很多方法,其实归根结底是围绕cors机制来实现(除了nginx反向代理)的,具体就是服务端发送
Access-Control-Allow-Origin
以及相关响应头,来通知浏览器有权访问资源。
前面讲了 nodejs 或nginx服务器端通过设置
Access-Control-Allow-Origin
,可以实现跨域,这里讲一下golang实现方式,当然php、java等也可以实现、原理相同。
示例1
package main import ( "net/http" ) func cors(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") // 允许访问所有域,可以换成具体url,注意仅具体url才能带cookie信息 w.Header().Add("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token") //header的类型 w.Header().Add("Access-Control-Allow-Credentials", "true") //设置为true,允许ajax异步请求带cookie信息 w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") //允许请求方法 w.Header().Set("content-type", "application/json;charset=UTF-8") //返回数据格式是json if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return } f(w, r) } } func index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello Golang")) } func main() { http.HandleFunc("/", cors(index)) http.ListenAndServe(":8000", nil) }
示例2
gin框架跨域中间件
package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() r.Use(Cors())//默认跨域 r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run(":8090") } func Cors() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method origin := c.Request.Header.Get("Origin") if origin != "" { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization") c.Header("Access-Control-Allow-Credentials", "true") c.Set("content-type", "application/json") } //放行所有OPTIONS方法 if method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) } c.Next() } }
gin有个官方的跨域中间件
https://github.com/gin-contri…
注意 :
某些简单请求不会触发 CORS 预检请求
。
Content-Type
的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded 默认
现在应用中越来越多前端和服务端都采用json通讯,如vue等。
要求前端
Content-Type
设置为 application/json,且是post请求,这属于复杂请求,将触发 CORS 预检请求
。即浏览器会先发送一次options请求,同意后才继续发送post请求。
当发送这种请求时,在浏览器的network会发现两条请求。同时在服务端接收前端参数时需要注意,以前通过get 、post方法会失效。
具体接收参数方法,php语言为 file_get_contents(‘php://input’) 。
golang语言
net/http
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" ) func cors(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") // 允许访问所有域,可以换成具体url,注意仅具体url才能带cookie信息 w.Header().Add("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token") //header的类型 w.Header().Add("Access-Control-Allow-Credentials", "true") //设置为true,允许ajax异步请求带cookie信息 w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") //允许请求方法 w.Header().Set("content-type", "application/json;charset=UTF-8") //返回数据格式是json if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return } f(w, r) } } type User struct { Username string `json:"username"` Password string `json:"password"` } func index(w http.ResponseWriter, r *http.Request) { body, _ := ioutil.ReadAll(r.Body) fmt.Println(string(body)) var user User if err := json.Unmarshal(body, &user); err == nil { fmt.Println(user) } else { fmt.Println(err) } w.Write([]byte("Hello Golang")) } func main() { http.HandleFunc("/", cors(index)) http.ListenAndServe(":8000", nil) }
gin 框架
对于gin框架我们就需要bind来解决这个问题
示例
type User struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func Login(c *gin.Context) { var u User err :=c.BindJSON(&u) fmt.Println(err) fmt.Println(u) }
先建一个结构体user,再使用BindJSON绑定,将request中的Body中的数据按照JSON格式解析到User结构体中。
需要注意:
- binding:”required” 字段对应的参数未必传没有会抛出错误,非banding的字段,对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。
- 结构体字段类型和所传参数类型要一致。
Bind的实现都在 gin/binding
里面. 这些内置的Bind都实现了 Binding
接口, 主要是 Bind()
函数.
- context.BindJSON() 支持MIME为application/json的解析
- context.BindXML() 支持MIME为application/xml的解析
- context.BindYAML() 支持MIME为application/x-yaml的解析
- context.BindQuery() 只支持QueryString的解析, 和Query()函数一样
- context.BindUri() 只支持路由变量的解析
- Context.Bind() 支持所有的类型的解析, 这个函数尽量还是少用(当QueryString, PostForm, 路由变量在一块同时使用时会产生意想不到的效果), 目前测试Bind不支持路由变量的解析, Bind()函数的解析比较复杂, 这部分代码后面再看
总结:
- 通常在解决跨域问题时,通过在服务端设置head请求的方式比较便利。
- 设置”Access-Control-Allow-Origin”为”*”匹配所有域名,但无法带cookie,影响登录。设置具体域名则不受影响。
-
前端
Content-Type
设置为 application/json时,服务端在接收参数数据方式不同。
参考:
https://developer.mozilla.org…
https://blog.csdn.net/qq_3796…
http://www.okyes.me/2016/05/0…
https://www.cnblogs.com/CyLee…
更多 golang开发资料
,请点击下方目录。