跨領域是開發中常會遇到的場景,也是面試中常會討論的問題。掌握常見的跨域解決方案及其背後的原理,不僅可以提高我們的開發效率,還能在面試中表現的更加游刃有餘。
因此今天就來跟大家從前端的角度來聊聊解決跨域常見的幾種方式。
在講跨域之前,我們先來看看URL的組成內容:
一個URL的組成,通常包含協定、主機名稱、連接埠號碼、路徑、查詢參數和錨點幾個部分。
這裡展示了一個URL的範例:
https://www.example.com:8080/path/resource.html?page=1&sort=desc#header
在上述範例中:
● 協定為HTTPS
● 主機名為www.example.com
● 端口編號為8080
● 路徑為/path/resource.html
● 查詢參數為page=1&sort=desc
● 錨點為header
##所謂跨域,指的是請求URL中協定、主機名稱、連接埠號碼中任一個部分不相同。
以上述URL為例,以下幾種寫法都算是和它跨域:http://www.example.com:8080/ // 协议不同 https://www.example.a.com:8080/ // 主机名不同 https://www.example.com:8081/ // 端口号不同
跨域問題的出現是受限於瀏覽器的同源策略。
所謂的同源策略,其實是瀏覽器的安全機制,用來限制一個網頁中網路請求僅能夠存取來自相同來源(網域名稱、協定和連接埠號碼皆相同)的資源,主要目的是防止惡意網站透過腳本竊取其他網站的敏感數據,保障使用者的隱私和安全。
解決跨域問題最常使用的方案是使用代理伺服器。
代理伺服器解決跨網域問題其實是抓住了同源策略只受限於瀏覽器存取伺服器,對於伺服器存取伺服器並沒有限制的特點,作為中間伺服器做了一個請求轉送的功能。
具體來說,就是前端工程師編寫的網頁運行在由webpack等腳手架搭建的代理伺服器上,當前端網頁在瀏覽器中發起網絡請求時,其實這個請求是發送到代理伺服器上的,然後代理伺服器會將請求轉送給目標伺服器,再將目標伺服器回傳的回應轉送給客戶端。 代理伺服器在過程中扮演了一個中轉的角色,可以對請求和回應進行一些修改、過濾和攔截,以實現一些特定的功能。因為前端網頁運行在代理伺服器上,所以不存在跨域問題。
那麼在線上環境和開發環境下,代理伺服器是如何做請求轉送的呢?nginx來做反向代理,從而把前端的請求轉送到目標接口上。
nginx是一個輕量級高並發的web伺服器,基於事件驅動,而且跨平台,window和Linux都可以進行設定。它作為代理伺服器來解決開發中的跨域問題的主要方法就是監聽線上前端網址的運行端口,然後碰到包含特殊標記的請求後就進行請求轉發 。
http-proxy-middleware中間件實現的。而http-proxy-middleware中介軟體的核心又是對http-proxy的進一步封裝。
這裡先展示一下在專案中使用http-proxy-middleware來實作請求轉送功能的範例程式碼:
const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = { server: { proxy: { // 将 /api/* 的请求代理到 http://localhost:3000/* '/api': { target: 'http://localhost:3000', changeOrigin: true, pathRewrite: { '^/api': '/' } } } } };接著
我們可以自己使用原生node,借助http-proxy庫來建立一個具有請求轉發功能的代理伺服器Demo,有興趣的朋友可以自己測試玩玩:
1. 首先需要建立一個空資料夾(全英命名)作為專案資料夾,然後使用npm init -y指令將專案升級為node的專案:
npm init -y
2. 接著在專案根目錄下建立一個index.html檔案用於發起跨域請求:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>请求转发测试</title> </head> <body> <h1>请求转发测试</h1> <p id="message"></p> <script> fetch('/api/login') .then(response => response.text()) .then(data => { document.getElementById('message').textContent = data; }); </script> </body> </html>
3. 接著在專案根目錄下新建index .js檔案來寫服務端的程式碼。
index.js檔案是實作具有請求轉送功能的代理伺服器的核心檔案。
const http = require('http'); const httpProxy = require('http-proxy'); const fs = require('fs'); const path = require('path'); // 创建代理服务器实例 const proxy = httpProxy.createProxyServer({}); // 创建HTTP服务器 const server = http.createServer((req, res) => { if (req.url === '/' || req.url.endsWith('.html')) { // 读取HTML文件 const filename = path.join(__dirname, 'index.html'); fs.readFile(filename, 'utf8', (err, data) => { if (err) { res.writeHead(500); res.end('Error reading HTML file'); } else { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(data); } }); } else if (req.url.startsWith('/api')) { // 重写路径,替换跨域关键词 req.url = req.url.replace(/^\/api/, ''); // 将请求转发至目标服务器 proxy.web(req, res, { target: 'http://localhost:3000/', changeOrigin: true, }); } }); // 监听端口 server.listen(8080, () => { console.log('Server started on port 8080'); });
4. 接著編寫目標伺服器target.js檔案的內容,用於測試跨網域存取:
const http = require('http'); const server = http.createServer((req, res) => { if (req.url.startsWith('/login')) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('我是localhost主机3000端口下的方法,恭喜你访问成功!'); } else { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, world!'); } }); server.listen(3000, () => { console.log('Target server is listening on port:3000'); })
5. 打开终端,输入启动目标服务器的命令:
node ./target.js //项目根目录下执行
6. 再开一个终端启动代理服务器,等待浏览器端发起请求就可以啦:
node ./index.js //项目根目录下执行
7. 最后在浏览器里访问http://localhost:8080, 打开控制台即可查看效果:
可以发现,浏览器network模块的网络请求确实是访问的8080端口的方法,但是我们的服务器默默的做了请求转发的功能,并将请求转发获取到的内容返回到了前端页面上。
其实http-proxy是对node内置库http的进一步封装,网络请求的核心部分还是使用http创建一个服务器对象去访问的。感兴趣的同学可以再读读http-proxy的源码~
除了代理服务器这种绕过浏览器同源策略的解决方式外,从前端的角度解决跨域问题还有如下一些常见的方法:
JSONP的原理是通过动态创建3f1c4e4b6b16bbbd69b2ee476dc4f83a标签,向服务器发送请求并在请求URL中传递一个回调函数名(通常是在本地定义的函数名),服务器在返回的数据中将这个回调函数名和实际数据一起封装成一个JavaScript函数的调用,返回给客户端,客户端利用该回调函数对数据进行处理。
JSONP之所以能够跨域请求数据,是因为浏览器对于3f1c4e4b6b16bbbd69b2ee476dc4f83a标签的请求不会受到同源策略的限制。
需要注意的是,使用JSONP技术的前提是服务器需要支持JSONP的方式,即在返回的数据中包含回调函数名和实际数据的封装,否则客户端无法处理返回的数据。
此外,JSONP只支持GET请求,不支持POST等其他HTTP请求方式,因为3f1c4e4b6b16bbbd69b2ee476dc4f83a标签只支持GET请求。
因此JSONP这种方式在我们的开发中使用的场景不多。
CORS全称为Cross-Origin Resource Sharing,它通过HTTP头部信息告诉浏览器哪些跨域请求是被允许的,从而实现安全的跨域访问。
CORS解决跨域需要浏览器端和服务器端的配合。原理是在服务器端设置HTTP头部信息,告诉浏览器允许哪些源(域名、协议、端口)访问服务器上的资源,如果请求的源不在允许的列表中,则浏览器将拒绝访
问。
服务器可以通过设置Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Allow-Methods等HTTP头部信息来控制跨域访问权限。
具体地,当浏览器发起跨域请求时,会先发送一个OPTIONS请求(预检请求),询问服务器是否允许该跨域请求。
服务器接收到该请求后,根据请求中的HTTP头部信息判断是否允许该请求。
如果允许,则返回相应的HTTP头部信息告知浏览器可以继续发送真正的跨域请求。如果不允许,则返回一个错误状态码,告诉浏览器该请求被拒绝。
预检请求时,请求头常见参数有:
请求头 | 值 |
---|---|
Origin | 表示请求的源地址,即发起跨域请求的域名 |
Access-Control-Request-Method | 表示实际请求采用的HTTP方法 |
Access-Control-Request-Headers | 表示实际请求中所携带的额外请求头信息,比如自定义请求头等 |
预检请求时,响应头常见参数有:
响应头 | 值 |
---|---|
Access-Control-Allow-Origin | *、origin... |
Access-Control-Allow-Headers | POST, GET, PUT, DELETE, OPTIONS |
Access-Control-Allow-Methods | Content-Type, Authorization.. |
Access-Control-Allow-Credentials | true |
Access-Control-Max-Age | 86400 |
要注意的是,使用CORS的前提是伺服器需要設定相關的HTTP頭部訊息,且瀏覽器支援CORS。此外,CORS只支援現代瀏覽器,對於一些老舊的瀏覽器可能不支援CORS。
例如WebSocket、postMessage等等
近年來,隨著前後端技術的快速發展,前後端獨立開發逐漸成為主流的開發模式。前後端程式設計師只需約定好接口,然後獨自進行對應模組的開發,最後進行接口聯調即可。 在介面聯調過程中,開發環境下的跨域就是一個需要解決的問題。
除此之外,當前後端專案打包上雲後,前端頁面透過線上位址存取後台介面時,線上環境下的跨域也是一個需要解決的問題。
本文講述了幾個常見的跨域解決方案,這些方案各有優缺點,大家可以根據實際情況選擇適合的方案來解決對應的跨域問題~
#推薦教學:nginx教學
#以上是如何解決跨域?常見解決方案淺析的詳細內容。更多資訊請關注PHP中文網其他相關文章!