什么是跨域,为什么要跨域?
跨域(Cross-Origin)指的是在浏览器中,当一个 web 应用程序试图访问不同域名、不同端口或不同协议的资源时,就会发生跨域请求,此时浏览器的同源策略(Same-Origin Policy)就会进行拦截,他是同源策略是一种安全机制,它限制了网页中的 JavaScript 代码只能访问同源(相同协议、主机和端口)的资源,以防止恶意网站通过 JavaScript 访问用户的敏感信息。
那我们为什么要跨域呢?
- 前端分离的项目:两者是运行再不同一个端口或者说是不同一个域名的,比如前端运行在localhost:9090而后端却运行在localhost:9092端口,他们传输数据就需要跨域才可以。
- 第三方资源集成:网站可能不仅需要后端的数据还需要第三方资源,通俗就是别的网站上的内容我也想要借用,此时别的网站也就是另外一个域名,跨过去就又是跨域了。
- 跨域通信:在某些场景下,不同域名的网页需要进行通信、数据交换或共享信息,例如使用跨域 Ajax 请求获取数据。
本篇文章主要说前端设置 代理服务器 的方式进行处理,后端讲解通过跨域资源共享(CORS机制 来实现跨域请求。
主要讲解localhost的不同端口发送的跨域请求,不同域的跨域请求是一样的流程下篇文章讲解什么是域以及域和ip地址的关系。
1.前端跨域处理方式以及原理
先看前端的vue项目,我们先创建一个vue项目然后找到其中的vue.config.js 将这段代码复制上去
//前端跨域处理方式
module.exports = {
devServer: {
port: 9090, // 自定义端口
proxy: {//设置跨域请求
'/api': { //只要是api开头的就会转到后端
target: 'http://localhost:9092', // 后端监听的地址
changeOrigin: true,
pathRewrite: {
'^/api': '/api'
}
}
}
}
}
* 前端代码解释
port就是设置前端运行的端口位置,proxy就是代理的意思 , 之后设置当在前端的请求中如果有/api路径的ajax请求的话,就会把发送请求的端口改为http://localhost:9092,也就是说前端要发送的请求让http://localhost:9092来代替发送,**changeOrigin**意思就是是否开启代理,最后**pathRewrite**意思是路径代替,比如原来的请求默认是 本地端口+路径也就是=
(http://localhost:9090/api/login)对吧,此时路径代替('^/api': '/api')的意思就是将api及其以前的部分代替成api,那么此时的路径是否就变为了api/login,然后再在路径的前面加上发送请求的代理端口,此时发送出来的请求就是http://localhost:9092/api/login 看最后的结果就是通过前面的代理配置然后得到了真正的后端需要的路径。
然后就能够成功发送给后端了,后续等待后端返回数据时,在浏览器看来它发送请求的地方时代理服务器http://localhost:9092,它接收的数据是
后端在http://localhost:9090端口发送的,那么请求的发送和数据的接收是在同一个端口,之后就判断是符合同源协议的。此时也就实现了跨域请求数据。
2后端跨域处理方式以及原理
这里为了更方便多人观看我后端用的是net/http包中的go原生语言中的http请求创建服务器、处理函数的,并没有使用gin框架
先看一眼我后端的创建服务器和请求然后就讲解跨域原理
func main() {
// 定义接口处理函数
loginHandler := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, api/login!")
}
// 注册接口处理函数
http.HandleFunc("api/login", loginHandler)
// 创建服务器并设置跨域请求的响应头
server := &http.Server{
Addr: ":9092",
Handler: corsMiddleware(http.DefaultServeMux),
}
// 启动服务器
server.ListenAndServe()
}
// 中间件函数,用于设置跨域请求的响应头
func corsMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 允许所有域名访问,可以根据实际需求进行修改
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:9090") // 前端的地址 //指定允许访问该资源的外域 URI,对于携带身份凭证的请求不可使用通配符*
// 允许的请求方法
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") //指明实际请求所允许使用的 HTTP 方法
// 允许的请求头字段
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")//指明实际请求中允许携带的首部字段
// 预检请求的缓存时间,单位为秒
w.Header().Set("Access-Control-Max-Age", "3600")
fmt.Println("前端发送了option")
// 如果是预检请求,直接返回
if r.Method == http.MethodOptions {
return
}
fmt.Println("前端发送了post")
// 调用下一个处理函数
handler.ServeHTTP(w, r)
})
}
* 后端代码解释
我们看我们后端是先开启了一个9092服务器的端口,之后设置了一个中间件 corsMiddleware 处理函数 设置了一个api/login路径的处理函数,
1.这里简单说下什么是中间件:
在 Go 语言中,中间件是一种函数,位于服务器和实际请求处理函数之间,允许您在请求达到处理函数之前修改请求或响应。中间件常用于实现横切关注点,如日志记录、身份验证、授权,以及在这种情况下的 CORS 处理。也就是说任何要走到9092端口的数据都要先经过中间件函数,然后再走对应路径的处理函数。
2.前端发送请求的流程:
前端在发送ajax请求的时候会让浏览器默认先发送一个OPTIONS请求,OPTIONS请求到达后端的中间的时候后端会告诉OPTIONS请求,后端允许什么域名什么方法去发起请求,然后把这些数据返回给浏览器(**<==也就是后端中间件的作用),之后浏览器判断自己即将要发送的POST请求的域名、方法、字段是否符合后端的设置,如果符合的话,此时就会发送POST**请求,如果不符合的话也就没有必要发送POST请求了,就会直接被同源策略给拦截住了。
3.接下来设置一下前端请求
我们在前端没有设置跨域的请求下,让前端运行在9090端口,此时在9090端口的地方发送了一个目的地是9092端口的URLhttp://localhost:9092/api/v1/login
(这里我用的是axios请求,但本质axios请请求就是封装了ajax请求,所以大家还认为是ajax请求就好)
4.后端得到请求后的处理流程
当发送之后我们后端得到的结果就应该是第一次浏览器发送OPTIONS请求,然后后端打印了 “前端发送了option” 然后后端就return了,之后浏览器真正发送post请求的时候,后端再一次进入了中间件并打印了 “前端发送了option” 然后第二次请求是POST请求,所以进入到了后续,又一次打印了 “前端发送了post”
之后就继续进行路径对应的处理函数了。
这里我们测试一下如果后端允许的地址不包含前端端口的话我们设置为允许3030端口 w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3030")
此时前端在9090端口的地方发送了http://localhost:9092/api/v1/login 目的地的post请求,此时后端就只会打印一个 “前端发送了option” ,因为同源策略已经把前端要发送的POST请求给拦截了。
最后最后给大家说一下我在测试前后端跨域时候遇见的坑。
1.第一个坑
在前端写发送请求测试的时候一定不能发送get请求,因为get请求不会让浏览器先发送一个OPTIONS请求去探路,我一开始并不知道,所以这就导致我在测试找跨域原理的时候用的get请求我就发现为什么我无论后端开不开跨域,前端开不开代理得到的结果都是一样的。让我测试了无数遍,开始质疑跨域有什么意义。。。还有一件事就是当前端发送POST请求的时候要携带参数才行,如果不携带参数浏览器会认为是简单请求,也不会先发送OPTIONS请求去探路。
2.第二个坑
我们在做测试的时候如果测试中参数都没有问题的话,那么浏览器会正常先发送一次OPTIONS请求,然后再发送POST请求,这样会得到你预期的效果,但是!!!如果这个时候你说没看清楚,然后又一次重启项目,想要再测一次,你就会发现这次浏览器只是发送了POST请求,然后又会质疑是不是自己刚看错了。。经过尝试我得知,浏览器是有记忆的,所以同样的路径,浏览器第一次测试的时候就已经记住了结果,所以第二次测试的时候就不会再发送OPTIONS请求了,而是直接POST请求,所以想要第二次测试的话,就改一下前端运行端口,和后端跨域允许范围,就能够重新测试了。
* 后端跨域小tips
我们都知道在后端的http.HandlerFunc(func(w http.ResponseWriter, r http.Request) {}函数参数中我们是可以同通过w和r获得发来请求的请求地址、请求头等等信息的,(如果是gin框架可以通过context获得相关信息)所以我们可以把允许访问的地址设置为从r中提取的请求地址,这样就设置为所有地址都可以实现跨域请求了。至于说为什么不用 * 来表示所有请求都可以跨域是因为:对于携带身份凭证的请求不可使用通配符,比如前端发送的携带了cookie身份验证,用 * 就不能实现跨域请求了。
版权归原作者 纪佰伦 所有, 如有侵权,请联系我们删除。