參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#預請求javascript
簡而言之,在跨域而且嘗試添加一些特殊頭及自定義頭的狀況下,因爲瀏覽器的安全機制,會加多一次OPTIONS預請求(詢問請求),與跨域服務器協商能夠設置的頭部信息,能夠容許的HTTP協議等等信息。php
以以下圖一次跨域請求爲例。html
圖中代碼以下前端
1 var settings = { 2 type: "POST", 3 url: 'http://www.movesun.com/cors.php?allow_method=PUT', 4 contentType: "application/json", 5 dataType:"json", 6 data : { 7 "name" : "lvyahui" 8 }, 9 xhrFields : { 10 // withCredentials : true 11 }, 12 success: function(resp) { 13 console.log(resp); 14 } 15 , 16 headers: { 17 appkey:"87a8ea08-dbaa-11e6-b3f9-7056818a4db5", 18 "X_forwarded-for":"10.104.239.XXX" 19 } 20 }; 21 $.ajax(settings);
能夠看到,這段代碼在movesun.com網站下,嘗試向www.movesun.com發送跨域POST 請求,而且有自定義頭(Content-Type設置了application/json類型也是緣由之一),所以瀏覽器在發送真實post請求以前,發起了一個OPTIONS請求詢問。java
請求之因此能夠成功,是由於後端服務器正常處理了OPTIONS請求,而且響應了正確的跨域響應頭,後端代碼cors.php以下nginx
1 <?php 2 3 if('OPTIONS' === $_SERVER['REQUEST_METHOD']){ 4 header('Access-Control-Allow-Origin:*'); 5 header('Access-Control-Allow-Headers:appkey,X_forwarded-for,Content-Type'); 6 header('Access-Control-Allow-Methods:' . (isset($_GET['allow_method']) ? $_GET['allow_method'] : 'OPTIONS')); 7 //header('Access-Control-Allow-Credentials:true'); 8 exit(0); 9 } 10 11 header('Access-Control-Allow-Origin:*'); 12 13 echo json_encode(array( 14 'code' => 0, 15 'msg' => '', 16 'data' => array() 17 ));exit(0);
能夠看到服務器判斷請求類型爲OPTIONS時,指定了以下幾個Http響應頭ajax
一、Access-Control-Allow-Origin : 跨域服務器容許的來源地址(跟請求的Origin進行匹配),能夠是*或者某個確切的地址,不容許多個地址。固然後臺代碼能夠動態判斷來源地址進行動態設置,這主要是由於有時須要容許任意來源訪問,而且要攜帶Cookie,此時須要明確指定地址(緣由在文後常見問題中說明),下面這段PHP代碼和Java代碼(注意Java代碼中Cookie沒有取端口,由於Cookie端口不一樣也算同域,能夠訪問到)就是取來源地址並響應chrome
1 if (isset($_SERVER['HTTP_REFERER'])) { 2 $urls = parse_url($_SERVER['HTTP_REFERER']); 3 $url = $urls['scheme'] . '://' . $urls['host']; 4 if (isset($urls['port'])) { 5 $url .= ':' . $urls['port']; 6 } 7 } else { 8 $url = '*'; 9 } 10 11 header("Access-Control-Allow-Origin: " . $url);
1 public void filter(ContainerRequestContext requestContext) throws IOException { 2 String origin = requestContext.getHeaderString("Origin"); 3 if(origin != null && !origin.trim().equals("") 4 // postMan 請求的protocol 是 chrome-extension:// 5 && origin.startsWith("http://")){ 6 URL url = new URL(origin); 7 String strUrl = url.getProtocol() + "://" + url.getHost(); 8 if(url.getPort() > 0){ 9 strUrl += ":" + url.getPort(); 10 } 11 originUrl = strUrl; 12 if(!cookieDomainAuto 13 && (sysConfig.getCookieDomain() == null || sysConfig.getCookieDomain().equals(""))){ 14 cookieDomainAuto = true; 15 } 16 if(cookieDomainAuto){ 17 // 動態判斷 cookie domain 18 if(url.getHost().matches(PATTERN_IP)){ 19 // IP 20 sysConfig.setCookieDomain(url.getHost()); 21 } else { 22 int start = url.getHost().lastIndexOf('.',url.getHost().lastIndexOf('.') - 1); 23 String domain; 24 if(start > 0){ 25 domain = url.getHost().substring(start + 1); 26 }else{ 27 domain = url.getHost(); 28 } 29 // domain 30 sysConfig.setCookieDomain(domain); 31 } 32 } 33 } 34 }
二、Access-Control-Allow-Methods:跨域服務器容許的請求方法。經測試發現,不論Access-Control-Allow-Methods設置爲簡單請求仍是複雜請求類型,全部的簡單的請求(GET,HEAD,POST)也是能夠正常請求的。json
三、Access-Control-Allow-Headers:跨域服務器容許客戶端添加或自定義哪些http 頭。後端
下面是這兩次請求的報文
OPTIONS請求報文
1 OPTIONS http://www.movesun.com/cors.php HTTP/1.1 2 Host: www.movesun.com 3 Proxy-Connection: keep-alive 4 Pragma: no-cache 5 Cache-Control: no-cache 6 Access-Control-Request-Method: POST 7 Origin: http://movesun.com 8 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36 9 Access-Control-Request-Headers: appkey, content-type, x_forwarded-for 10 Accept: */* 11 Referer: http://movesun.com/ 12 Accept-Encoding: gzip, deflate, sdch 13 Accept-Language: zh-CN,zh;q=0.8 14 15 16 HTTP/1.1 200 OK 17 Date: Fri, 10 Mar 2017 05:48:07 GMT 18 Server: Apache 19 Access-Control-Allow-Origin: * 20 Access-Control-Allow-Headers: appkey,X_forwarded-for,Content-Type 21 Access-Control-Allow-Methods: POST 22 Vary: User-Agent,Accept-Encoding 23 Content-Encoding: gzip 24 Content-Length: 20 25 Content-Type: text/html 26 X-Cache: MISS from SK-SQUIDDEV-11 27 X-Cache-Lookup: MISS from SK-SQUIDDEV-11:8080
POST請求報文
1 POST http://www.movesun.com/cors.php HTTP/1.1 2 Host: www.movesun.com 3 Proxy-Connection: keep-alive 4 Content-Length: 12 5 Pragma: no-cache 6 Cache-Control: no-cache 7 Origin: http://movesun.com 8 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36 9 Content-Type: application/json 10 Accept: application/json, text/javascript, */*; q=0.01 11 X_forwarded-for: 10.104.239.194 12 appkey: 87a8ea08-dbaa-11e6-b3f9-7056818a4db5 13 Referer: http://movesun.com/ 14 Accept-Encoding: gzip, deflate 15 Accept-Language: zh-CN,zh;q=0.8 16 name=lvyahui
從報文中能夠看出,OPTIONS請求後臺能夠拿到URL中的GET參數,也就是說,若是真實請求是GET請求,則後端在處理來詢問的OPTIONS請求時,就能夠獲取到全部查詢參數了。如mozilla官網所寫,筆者調試發現,一些跨域請求,即使拋出了錯誤的狀況下,請求也真的到了後臺服務器,只是響應被瀏覽器攔截了。
另外,有時不想在後臺代碼中處理OPTIONS請求,則能夠在nginx server節點下作以下配置,表示攔截處理全部OPTIONS請求。
1 location ^~ / { 2 if ($request_method = OPTIONS ) { 3 add_header Content-Length 0; 4 add_header Content-Type text/plain; 5 add_header 'Access-Control-Allow-Origin' '*'; 6 add_header 'Access-Control-Allow-Methods' '*'; 7 add_header 'Access-Control-Allow-Headers' 'appkey,X_forwarded-for,Content-Type'; 8 return 200; 9 } 10 }
下面是一些跨域下常見的一些問題
1 if('OPTIONS' === $_SERVER['REQUEST_METHOD']){ 2 header("HTTP/1.1 405 Method Not Allowed"); 3 exit(-1); 4 }
1 if('OPTIONS' === $_SERVER['REQUEST_METHOD']){ 2 header('Access-Control-Allow-Origin:http://movesun.com'); 3 header('Access-Control-Allow-Headers:appkey,X_forwarded-for,Content-Type'); 4 header('Access-Control-Allow-Methods:' . (isset($_GET['allow_method']) ? $_GET['allow_method'] : 'OPTIONS')); 5 header('Access-Control-Allow-Credentials:true'); 6 exit(0); 7 } 8 header('Access-Control-Allow-Origin:http://movesun.com'); 9 header('Access-Control-Allow-Credentials:true'); 10 echo json_encode(array( 11 'code' => 0, 12 'msg' => '', 13 'data' => array() 14 ));exit(0);
注意:文中的測試接口 在 http://movesun.com/cors.php 或者 http://www.movesun.com/cors.php,感興趣的讀者能夠用這個接口測試。