您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關為什么要研究跨域問題,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
跨域,老生常談的問題
簡述
作為一只前端菜鳥,跨域方面只懂得JSONP和CORS,并未曾深入了解。但隨著春招越來越近,就算是菜鳥也要猛振翅膀。近幾日仔細研究了跨域問題,寫下這篇文章,希望對開發者們有所幫助。在讀本文前,希望您對以下知識略有了解。
瀏覽器同源策略
nodejs
iframe
docker, nginx
我們為何要研究跨域問題
因為瀏覽器的同源策略規定某域下的客戶端在沒明確授權的情況下,不能讀寫另一個域的資源。而在實際開發中,前后端常常是相互分離的,并且前后端的項目部署也常常不在一個服務器內或者在一個服務器的不同端口下。前端想要獲取后端的數據,就必須發起請求,如果不錯一些處理,就會受到瀏覽器同源策略的約束。后端可以收到請求并返回數據,但是前端無法收到數據。
多種跨域方法
跨域可以大概分為兩種目的
前后端分離時,前端為了獲取后端數據而跨域
為不同域下的前端頁面通信而跨域
為前后端分離而跨域
Cross Origin Resource Share (CORS)
CORS是一個跨域資源共享方案,為了解決跨域問題,通過增加一系列請求頭和響應頭,規范安全地進行跨站數據傳輸
請求頭主要包括
請求頭 | 解釋 |
---|---|
Origin | Origin頭在跨域請求或預先請求中,標明發起跨域請求的源域名。 |
Access-Control-Request-Method | Access-Control-Request-Method頭用于表明跨域請求使用的實際HTTP方法 |
Access-Control-Request-Headers | Access-Control-Request-Headers用于在預先請求時,告知服務器要發起的跨域請求中會攜帶的請求頭信息 |
響應頭主要包括
響應頭 | 解釋 |
---|---|
Access-Control-Allow-Origin | Access-Control-Allow-Origin頭中攜帶了服務器端驗證后的允許的跨域請求域名,可以是一個具體的域名或是一個*(表示任意域名)。 |
Access-Control-Expose-Headers | Access-Control-Expose-Headers頭用于允許返回給跨域請求的響應頭列表,在列表中的響應頭的內容,才可以被瀏覽器訪問。 |
Access-Control-Max-Age | Access-Control-Max-Age用于告知瀏覽器可以將預先檢查請求返回結果緩存的時間,在緩存有效期內,瀏覽器會使用緩存的預先檢查結果判斷是否發送跨域請求。 |
Access-Control-Allow-Methods | Access-Control-Allow-Methods用于告知瀏覽器可以在實際發送跨域請求時,可以支持的請求方法,可以是一個具體的方法列表或是一個*(表示任意方法)。 |
客戶端只需按規范設置請求頭。
服務端按規范識別并返回對應響應頭,或者安裝相應插件,修改相應框架配置文件等。具體視服務端所用的語言和框架而定
SpringBoot 設置CORS例子
一個spring boot項目中關于CORS配置的一段代碼
HttpServletResponse httpServletResponse = (HttpServletResponse) response; String temp = request.getHeader("Origin"); httpServletResponse.setHeader("Access-Control-Allow-Origin", temp); // 允許的訪問方法 httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH"); // Access-Control-Max-Age 用于 CORS 相關配置的緩存 httpServletResponse.setHeader("Access-Control-Max-Age", "3600"); httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,token"); httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
JSONP 跨域
jsonp的原理就是借助HTML中的<script>標簽可以跨域引入資源。所以動態創建一個<srcipt>標簽,src為目的接口 + get數據包 + 處理數據的函數名。后臺收到GET請求后解析并返回函數名(數據)給前端,前端<script>標簽動態執行處理函數
觀察下面代碼
前端代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> var script = document.createElement('script'); script.type = 'text/javascript'; // 傳參并指定回調執行函數為getData script.src = 'http://localhost:8080/users?username=xbc&callback=handleData'; document.body.appendChild(script); // 回調執行函數 function handleData(res) { data = JSON.stringify(res) console.log(data); } </script> </body> </html>
后端代碼(nodejs)
var querystring = require('querystring'); var http = require('http'); var server = http.createServer(); server.on('request', function(req, res) { var params = querystring.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回設置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); var data = { user: 'xbc', password: '123456' } res.write(fn + '(' + JSON.stringify(data) + ')'); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
在該例子中,前臺收到的res是這樣的
前端頁面是這樣的
注意
JSONP既是利用了<srcipt>,那么就只能支持GET請求。其他請求無法實現
nginx 反向代理實現跨域
思路
既然瀏覽器有同源策略限制,那我們把前端項目和前端要請求的api接口地址放在同源下不就可以了?再結合web服務器提供的反向代理,便可以在前端和后端都不做配置的情況下解決跨域問題。
后端真實后臺地址:http://xxx.xxx.xxx.xxx:8085
后臺地址使用tomcat部署的spring boot項目 名為gsms_test
nginx服務器地址: http://xxx.xxx.xxx.xxx:8082
tomcat和nginx都是用docker架設的,做了端口轉發
使用條件:開發環境為linux系統
nginx /etc/nginx/conf.d/default.conf
配置代碼如下
server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; location / { # root /usr/share/nginx/html/dist; # 前端項目路徑 # index index.html index.htm; proxy_pass http://localhost:8001/; # 前端本機地址,實現自動更新 autoindex on; autoindex_exact_size on; autoindex_localtime on; } location /gsms_test/ { proxy_pass 后端真實地址; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }
不同域下頁面通信而跨域
window.name + iframe 跨域
window.name是瀏覽器中一個窗口所共享的數據,在不同的頁面(甚至不同域名)加載后依舊存在(如果沒修改則值不會變化),并且可以支持非常長的 name 值(2MB)。比如 a域的某頁面想獲取b域某頁面的數據,可以在b域中修改window.name值,a域切換到b域再切回來即可得到b域的window.name值。可是我們在開發中肯定不想頁面切來切去,所以就要結合iframe來實現。
示例 (以thinkjs實現)
a 域代碼如下
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>A 域</title> </head> <body> <h2>server A</h2> <script type="text/javascript"> function getData() { var iframe = document.getElementById('proxy'); iframe.onload = function () { var name = iframe.contentWindow.name; // 獲取iframe窗口里的window.name值 console.log(name) } // 由于iframe信息傳遞也受同源策略限制,所以在window.name被B域修改后,將iframe轉回A域下。以便獲取iframe的window.name值 iframe.src = 'http://127.0.0.1:8360/sub.html' } </script> <iframe id="proxy" src="http://127.0.0.1:8361/index.html" style="width: 100%" onload="getData()"> </iframe> </body> </html>
b 域代碼
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>New ThinkJS Application</title> </head> <body> <h2>server 2</h2> <script type="text/javascript"> window.name = 'user: xbc'; </script> </body> </html>
由于受同源策略限制,父頁面獲取跨域的iframe頁面的信息不全,所以要在iframe的window.name被B域修改后,轉為A域下的任一頁面(該一面不得修改window.name),在進行獲取。
由于iframe與父頁面相互訪問也受同源策略限制,所以要借助一代理頁面實現跨域。
個人認為有些麻煩,若有興趣請看前端如何用代理頁面解決iframe跨域訪問的問題?
總結
以上幾種皆是本人用過或測試過的跨域方法,還有postMessage,WebSocket等跨域方法由于從未接觸不做說明。在項目中具體使用那些方法還需具體考慮各種問題
情況 | 方法 |
---|---|
只有GET請求 | JSONP |
對兼容性及瀏覽器版本無要求 | CORS |
對兼容性及瀏覽器版本有要求 | iframe 或 服務器反向代理(linux 環境下開發) |
關于為什么要研究跨域問題就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。