1. 程式人生 > >跨域請求的幾種實現方式

跨域請求的幾種實現方式

跨域是瀏覽器的一種安全策略,是瀏覽器自身做的限制,不允許使用者訪問不同域名或埠或協議的網站資料。
只有域名(主域名【一級域名】和二級域名)、埠號、協議 完全相同的時候,才允許通訊。

通過前端實現跨域解決方案有:document.domain + iframe, window+name, HTML5 postMessage 。。。。詳見該篇文章
還有這篇

本文講述幾種通過後端實現跨域的解決方案:

1.jsonp

最常見的一種跨域方式,其背後原理就是利用了script標籤不受同源策略的限制,在頁面中動態插入了script,script標籤的src屬性就是後端api介面的地址,並且以get的方式將前端回撥處理函式名稱告訴後端,後端在響應請求時會將回調返還,並且將資料以引數的形式傳遞回去。

前端js:

 //1.jsonp 跨域(涉及到前後端) 原理是利用script標籤不受同源策略的限制 在頁面中動態插入script標籤 標籤的src即為請求url的地址
//並將前端獲取到請求資料之後的回撥函式作為引數附加在url地址後
var script = document.createElement('script');
 //注意  會有使用者名稱密碼 驗證的問題
script.src = "http://127.0.0.1:6666/tools/jsonpHandler?callback=_callback&username=admin&password=123456";
document.body.appendChild(script);
//回撥處理函式
var _callback = function(obj){ for(key in obj){ console.log("JSONP跨域測試 key: " + key + " value: " + obj[key]); } }

後端java:

@RequestMapping(value = "jsonpHandler")
public ModelAndView jsonpHandler(HttpServletRequest request, HttpServletResponse response) throws Exception{
        String
callback = request.getParameter("callback"); logger.info("callback: " + callback); JSONObject obj = new JSONObject(); obj.put("type", "jsonp"); obj.put("method", "jsonHandler"); response.setContentType("text/html; charset=UTF-8"); response.getWriter().write(callback + "(" + JSON.toJSONString(obj) + ")"); return null; }

2.CORS

Cross-Origin Resource Sharing(跨域資源共享)是一種允許當前域(origin)的資源(比如html/js/web service)被其他域(origin)的指令碼請求訪問的機制。
當使用XMLHttpRequest傳送請求時,瀏覽器如果發現違反了同源策略就會自動加上一個請求頭:origin,後端在接受到請求後確定響應後會在Response Headers中加入一個屬性:Access-Control-Allow-Origin,值就是發起請求的源地址(http://127.0.0.1:8888),瀏覽器得到響應會進行判斷Access-Control-Allow-Origin的值是否和當前的地址相同,只有匹配成功後才進行響應處理。

現代瀏覽器中和移動端都支援CORS(除了opera mini),IE下需要8+

前端js:

//2.CORS 涉及到前後端
$.ajax({
       type:"POST",
       datatype:"JSON",
       data:{
            "param1":"param1-value",
            "param2":"param2-value",
            "username":"admin",
            "password":"123456"
       },        
       url:"http://127.0.0.1:6666/tools/corsHandler",
       error:function(){
           alert("請求失敗!");
       },
       success:function(data){
           console.log("CORS跨域測試 data:");
           console.log(data);
       }
 });

後臺java:

 @RequestMapping(value = "corsHandler")
 public ModelAndView corsHandler(HttpServletRequest request,HttpServletResponse response) throws IOException{
       String param1 = request.getParameter("param1");
       String param2 = request.getParameter("param2");
       String requestUri = request.getRequestURI();
       String requestUrl = request.getRequestURL().toString();//本域名下請求地址 不是客戶端的地址

       Integer tempIndex = requestUrl.indexOf(requestUri);  
       //發出請求的源域名地址     
       String requestOrigin = requestUrl.substring(0, tempIndex);
      if(request.getHeader("Origin") != null ){//跨域請求
        requestOrigin = request.getHeader("Origin");
      }
      logger.info("requestUri: " + requestUri + "; requestUrl: " + requestUrl+ "; requestOrigin: " + requestOrigin);
      JSONObject obj = new JSONObject();
      obj.put("param1", param1);
      obj.put("param2", param2);
       //此方法allowOrigin中只能寫一個域名 因此改為與list對比的方式實現
       /*String allowOrigin = appConfig.getAllowOrigin();
      logger.info("allowOrigin: " + allowOrigin);
      response.setHeader("Access-Control-Allow-Origin", allowOrigin);*/
      response.setContentType("text/html; charset=UTF-8");  
      if(Constants.allowOriginList.
      contains(requestOrigin)){
           response.setHeader("Access-Control-Allow-Origin", requestOrigin);
       }
      response.getWriter().write( JSON.toJSONString(obj)); 
       return null;
}

3.nginx反向代理

此方法最為便利,前後端程式碼無需做特殊改變,只需在nginx.conf做配置,將本地請求地址轉向真正實現請求的url地址。
js程式碼:

//3.用nginx代理解決跨域問題
$.ajax({
        type:"POST",
        datatype:"JSON",
        data:{
                "param1":"nginx-param1-value",
                "param2":"nginx-param2-value",
                "username":"admin",
                "password":"123456"
        },
        url:"/apis/tools/nginxCrossOrigin",
        error:function(){
              alert("請求失敗!");
        },
        success:function(data){
             console.log("nginxCrossOrigin 跨域測試 data:");
             console.log(data);
        }
 });

java程式碼:

public ModelAndView nginxCrossOrigin(HttpServletRequest request,HttpServletResponse response) throws IOException{
      String param1 = request.getParameter("param1");
      String param2 = request.getParameter("param2");
      logger.info("param1: " + param1 + "; param2 : " + param2);

      JSONObject obj = new JSONObject();
      obj.put("param1", param1);
      obj.put("param2", param2);
      response.setContentType("text/html; charset=UTF-8");   
      response.getWriter().write( JSON.toJSONString(obj)); 
      return null;
 }

nginx.conf:

#for cross origin request
server {
        #在瀏覽器中訪問源域名地址時 使用埠2222
        listen 2222;

        #源域名地址下訪問/apis/***開頭的url地址 會自動將請求轉向http://127.0.0.1:14444/***
        location /apis {
           #$1 第一個()中的值
           rewrite ^.+apis/?(.*)$ /$1 break;
           include uwsgi_params;
           proxy_pass http://127.0.0.1:14444;
        }

        #發出請求的源域名地址
        location / {
           proxy_pass http://127.0.0.1:12222;
        }

 }

關於nginx和uwsgi的關係,可參考文章
(1 )首先nginx 是對外的服務介面,外部瀏覽器通過url訪問nginx,

(2)nginx 接收到瀏覽器傳送過來的http請求,將包進行解析,分析url,如果是靜態檔案請求就直接訪問使用者給nginx配置的靜態檔案目錄,直接返回使用者請求的靜態檔案,

如果不是靜態檔案,而是一個動態的請求,那麼nginx就將請求轉發給uwsgi,uwsgi 接收到請求之後將包進行處理,處理成wsgi可以接受的格式,併發給wsgi,wsgi 根據請求呼叫應用程式的某個檔案,某個檔案的某個函式,最後處理完將返回值再次交給wsgi,wsgi將返回值進行打包,打包成uwsgi能夠接收的格式,uwsgi接收wsgi 傳送的請求,並轉發給nginx,nginx最終將返回值返回給瀏覽器。

(3)要知道第一級的nginx並不是必須的,uwsgi完全可以完成整個的和瀏覽器互動的流程,但是要考慮到某些情況

a.安全問題,程式不能直接被瀏覽器訪問到,而是通過nginx,nginx只開放某個介面,uwsgi本身是內網介面,這樣運維人員在nginx上加上安全性的限制,可以達到保護程式的作用。

b.負載均衡問題,一個uwsgi很可能不夠用,即使開了多個work也是不行,畢竟一臺機器的cpu和記憶體都是有限的,有了nginx做代理,一個nginx可以代理多臺uwsgi完成uwsgi的負載均衡。

c.靜態檔案問題,用django或是uwsgi這種東西來負責靜態檔案的處理是很浪費的行為,而且他們本身對檔案的處理也不如nginx好,所以整個靜態檔案的處理都直接由nginx完成,靜態檔案的訪問完全不去經過uwsgi以及其後面的東西。

4.後臺跨域

前端向本地後臺發出請求,後臺再向api伺服器請求資料,然後再將請求到的資料返回給前端。
後臺請求函式實現:

 /**
     * @param url 請求地址
     * @param map 請求引數
     * @param charset 字符集
     * 
     * @return map - status 返回狀態 1-失敗 0-成功 Integer
     * @return map - result 失敗原因 or 請求成功後api的返回資訊 String
     *
     * */
@SuppressWarnings({ "resource", "deprecation" })
public static HashMap<String,Object> doPost(String url,Map<String,Object> map,String charset){  
        HttpClient httpClient = null;  
        HttpPost httpPost = null;  
        String result = "伺服器出錯啦!請稍後重試!"; 
        Integer status = 1;
        HashMap<String,Object> returnMap = new HashMap<String,Object>();
        try{  
            httpClient = new DefaultHttpClient();  
            httpPost = new HttpPost(url);  
            //設定引數  
           /* List<NameValuePair> list = new ArrayList<NameValuePair>();  
            Iterator<Entry<String, String>> iterator = map.entrySet().iterator();  
            while(iterator.hasNext()){  
                Entry<String,String> elem = (Entry<String, String>) iterator.next();  
                list.add(new BasicNameValuePair(elem.getKey(),elem.getValue()));  
            }  
            if(list.size() > 0){  
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list,charset); 
                httpPost.setEntity(entity);  
            }  */
            String jsonStr = Json.toJson(map);
           // String jsonStr = JSON.toJSONString(map);
            logger.info("jsonStr: " + jsonStr);
            logger.info("jsonStr2:" + JSON.toJSONString(map));
            StringEntity entity = new StringEntity(jsonStr, "UTF-8");
            entity.setContentType("application/json;charset=UTF-8");
            httpPost.setEntity(entity);


            HttpResponse response = httpClient.execute(httpPost);  
            if(response != null){  
                if(HttpStatus.SC_OK == response.getStatusLine().getStatusCode()){ 
                    HttpEntity resEntity = response.getEntity();  
                    if(resEntity != null){ 
                        status = 0;
                        result = EntityUtils.toString(resEntity,charset);  
                    }  
                }else{
                    status = 1;
                    result = "伺服器內部錯誤導致請求出錯!";
                }  
            }else{
                status = 1;
                result = "伺服器內部錯誤導致請求出錯!";
            }  
        }catch(Exception ex){  
            ex.printStackTrace();  
            status = 1;
            result = "後臺出現異常,exception: " + ex.toString();
        }  

        returnMap.put("status", status);
        returnMap.put("result", result);

        return returnMap;  
 }