Cross Domain Ajax 跨網域抓取資料(JSONP)

【如果覺得文章寫的好的話希望能按一下讚或分享喔】

 圖片來源

 

在一些瀏覽器端的語言 (例如 Javascript ) 當中,會利用 Same origin policy (同網域限制) 的概念,以保障資料傳輸的安全性,但有時候在系統設計的時候,就是會需要設計出這種系統架構,或是需要開放部份資料的 API ,來讓外部網域存取,此時就會需要 cross domain 取資料的方法,本篇文章介紹透過 JSONP 的方法,來達成跨網域取得資料的需求。

 

在 Wiki 對於 Same origin policy 的定義如下:

In computing, the same origin policy is an important security concept for a number of browser-side programming languages, such as JavaScript. The policy permits scripts running on pages originating from the same site to access each other’s methods and properties with no specific restrictions, but prevents access to most methods and properties across pages on different sites

 

中文的翻譯是,在電腦科學的領域, same origin policy 是一個重要的瀏覽器端語言的安全協定,此協定僅允許 script 在同個網域之間互相傳送資料,但是禁止不同網域之間互相取用方法與屬性。

ps. 這一份 文件裡有針對相關議題的更完整內容

 

Origin determination rules

簡單來說, Same origin policy 定義了 Origin determination rules ,即特定的網域才可互相 reference,表格整理如下:

URL 結果 原因
http://www.example.com/dir/page.html okay 網域與 protocol 相同
http://www.example.com/dir2/other.html okay 網域與 protocol 相同
http://www.example.com:81/dir/other.html Fail 網域與 protocol 相同,但 port不同
https://www.example.com/dir/other.html Fail 不同 protocol
http://en.example.com/dir/other.html Fail 不同網域
http://example.com/dir/other.html Fail 不同網域  (網域需完全相同)
http://v2.www.example.com/dir/other.html Fail 不同網域  (網域需完全相同)

 

方法:JSONP

JSONP ((JSON with Padding)是解決跨網域限制的方法之一,定義如下:

JSONP 是一个非官方的协议,它允许在服务器端集成 Script tags 返回至客户端,通过 Javascript callback 的形式实现跨域访问。  來源

所以,此種方法主要是透過 JSONP 動態 (根據 XMLHttpRequest ) 產生 JSON 資料,假設我們今天想要向 http://server2.example.com/RetrieveUser?UserId=xxx 拿資料,則我們會利用瀏覽器送一段  http://server2.example.com/RetrieveUser?UserId=1823 的指令過去,該 Server 即會根據取得的 UserId = 1823 ,產生對應的 JSON response ,例如:

   {"Name": "小明", "Id" : 1823, "Rank": 7}

 

JSONP 簡單範例

jQuery 在 1.2 版以後,就加入了對於 JSONP 的支援,只要使用 $.getJSON() 方法,即可達成跨 Domain 取得資料的實作,範例如下 (本範例從 flickr 截取資料,你可以從 Result tab 察看結果):

 

 

以上程式碼使用 $.getJSON 方法,夾帶 tags, tagmode, format 三個參數,送到 flickr 開放的 API :http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?

便可以取得回傳的 data ,並顯示在畫面上

 

URL 當中的 callback = ?

眼尖的人會注意到,透過 jsonp 的方法所傳送的 URL 當中,結尾都會帶一個問號 (?),這個問號會在取得 server 回傳值的時候,自動轉換成 call function 的 function name,而如果是一個匿名函式,則會轉換成一個帶有 time stamp 的函式名稱。

以上就是 server 端所提供的 jsonp 服務,接受 callback function 為參數,並將 callback function 的結果回傳給 client 端,當傳輸完成後,client 會自動執行 Server 回傳的 callback function,取得結果。

傳送架構圖如下:

image

 

remote Ajax call 實際範例

以下為一個實際範例:

http://design2u.me/example/remoteAjaxCall/

 

以上範例共有 4 個取資料的模式 (4個 Button) ,分別介紹如下

(1) 從同一個 domain 拿資料 (get same domain data)

(2) 從不同 domain 拿資料 (get different domain data)

(3) 從不同 domain 拿資料,透過 jsonp 格式 (get different domain data via jsonp)

(4) 從不同 domain 拿資料,透過 jsonp 格式 (get different domain data via jsonp)

 

從同一個 domain 拿資料

假設你只是要從同個網域 (或是同個專案資料夾) 下面拿資料,例如:

image

 

而 index.html 的主要 code 如下:

//從同樣的 domain 拿資料
	$("#getSameDomainData").click(function(){
		ajaxCall("jquery.min.js");
	});

function ajaxCall(target){

	//發出 ajax call
	var data = $.ajax({
		type: "POST",
		url: target,
	});

	//成功得到資料
	data.success(function( msg ) {
		$("#result").html(msg);
	});

	//取得資料失敗
	data.error(function( msg ) {
		$("#result").html("fail getting data");
	});

}

 

即可順利取得同個資料夾裡面檔案的內容,結果如下:

image 

 

從不同 domain 拿資料

而假設我們同樣想要引入外部資料,但是 domain name 不同的時候呢

而現在新的程式碼如下:

	//從不同的 domain 拿資料
	$("#getDifferentDomainData").click(function(){
		ajaxCall("http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js");
	});

function ajaxCall(target){

	//發出 ajax call
	var data = $.ajax({
		type: "POST",
		url: target,
	});

	//成功得到資料
	data.success(function( msg ) {
		$("#result").html(msg);
	});

	//取得資料失敗
	data.error(function( msg ) {
		$("#result").html("fail getting data");
	});

}

 

這次改成從 jquery 的 google CDN 拿同一份文件

但是卻得到 fail getting data:

image

主要原因即是我們想要進行 cross domain access 遭拒絕

 

從 flickr 拿資料,透過 jsonp 格式

事實上許多有名的網站都有開 RESTful API,讓別人可以透過 jsonp 的方式取得資料,例如 flcikr 的相片 API

http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?

 

透過 jquery 我們也可以輕易的夾帶參數送到 server ,取得對應的資料

例如以下的程式碼:

//從不同的 domain 拿資料 ( Server 端需使用 jsonp 格式)
	$("#getDifferentDomainDataViaJsonp").click(function(){
		
		var options = {
			tags: "mount rainier",
			tagmode: "any",
			format: "json"
		};
		ajaxCallJsonp("http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?",options);
	});

function ajaxCallJsonp(target,options){
	
	var data = $.getJSON(target,options);
	
	//empty content
	$("#result").html("");

	//成功得到資料
	data.success(function( msg ) {
		
		//flickr data
		if(target=="http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?"){
			$.each(msg.items, function(i,item){

			  
			  $("#result").append($("<img/>").attr("src", item.media.m));	
			  if ( i == 3 ) {
			  	return false;
			  }
			  
			});
		}
		//my data
		else if(target=="http://design2u.me/example/jsonp/index.php?callback=?"){
			$("#result").html('Your name is '+msg.fullname);
		}
		//other
		else{
			$("#result").html("取得資料成功,但未得到被定義的資料");
		}


		
	});

	//取得資料失敗
	data.error(function( msg ) {
		$("#result").html("fail getting data");
	});

}

 

讓我們可以傳送相關的參數後,帶出對應的 flikr 照片

結果如下:

image

 

從 PHP server 拿資料,透過 jsonp 格式

也可以試著自行架 jsonp server 試看看

以下範例可打造 PHP 的 jsonp server:

 

<?php
 $fname = $_GET['firstname'];
      if($fname=='Jeff')
      {
          //header("Content-Type: application/json");
		  //組成 $_GET['callback']({'fullname':'Jeff Hansen'})
         echo $_GET['callback'] . '(' . "{'fullname' : 'Jeff Hansen'}" . ')';

      }
?>

 

Client 端的 code 如下:

//從不同的 domain 拿資料 ( Server 端需使用 jsonp 格式)
	$("#getDifferentDomainDataViaJsonp2").click(function(){

		var options = {
			firstname:"Jeff"
		};

		ajaxCallJsonp("http://design2u.me/example/jsonp/index.php?callback=?",options);

	});

function ajaxCallJsonp(target,options){
	
	var data = $.getJSON(target,options);
	
	//empty content
	$("#result").html("");

	//成功得到資料
	data.success(function( msg ) {
		
		//my data
		else if(target=="http://design2u.me/example/jsonp/index.php?callback=?"){
			$("#result").html('Your name is '+msg.fullname);
		}
				
	});

	//取得資料失敗
	data.error(function( msg ) {
		$("#result").html("fail getting data");
	});

}

運作的邏輯與順序為:

(1) Client 透過 $.getJSON 方法,將參數  firstname: “Jeff” 傳送過去

(2) server 接到 request,並 GET 到 firstname:”Jeff”

(3) server 確認對應的邏輯,並回傳  $_GET['callback']({‘fullname’:’Jeff Hansen’})  給來源

(4) Client 接到 server 端給的 {‘fullname’:’Jeff Hansen’},並傳進 msg 這個變數

(5) 透過 msg.fullname ,取得 Jeff Hansen,顯示在螢幕上

 

 

從 JSP server 拿資料,透過 JSONP 格式

從 PHP 的範例可以發現,其實只要組成  $_GET['callback']({‘fullname’:’Jeff Hansen’})  這樣的回傳格式

即可將資料吐回 client 端,所以 JSP Server 只要透過以下語法,也可以組成合適的格式:

	/**
	 * 
	 * @param form
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	public String ajaxGetMybusAllDataJsonp(ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {

       // 取得 client 端傳來的參數
		String callback = request.getParameter("callback");

		//宣告 JSON 物件
		JSONObject res = new JSONObject();
		res.put("item", "item content");
		res.put("item2", "item content2");

		// 避免 browser 出現亂碼
		response.setContentType("text/html");
		response.setCharacterEncoding("utf-8");
		PrintWriter out = response.getWriter();
		
      // 組成 callback 格式
      out.print(callback + "(" + res.toString() + ")");
		out.flush();
		out.close();
		return null;
	}

 

範例下載

以上的幾個範例,可在 此連結 下載到

 

參考資料

以上的資料參考自以下連結:

Related Posts Plugin for WordPress, Blogger...
【如果覺得文章寫的好的話希望能按一下讚或分享喔】

彭其捷

想要做出很棒的產品,所以我寫程式,也研究介面 (UI) 與使用者經驗 (UX),並思考管理的法則。

【Profile】http://about.me/divaka

twitter facebookgooglepluslinkedinbloggertumblrflickryoutubedribbblerssemail

  • 奕孝 陳

    這篇很實用唷~感謝分享^^

  • 鈞立 王

    簡潔明瞭!謝謝您的分享~

  • 李孟濂

    老師您好:
    我想製作一個html+js再不建立伺服器及第三端的方式跨域擷取。
    但是試過一些方式都不太順利..
    想請問老師,jsonp能夠運用在『公開資訊觀測站』嗎?還是應該利用其他方式呢?
    callpack的回傳使用要怎麼寫才是正確的呢?
    json裏頭的物件要怎麼做才能令網外的伺服器接受呢?

    在這裡先說聲不好意思,我是剛起步,如果有些問題冒昧到老師的智慧,我先說聲抱歉!
    希望老師能夠授予知識,萬分感謝

  • http://www.bulgaria-web-developers.com/en/home Dimitar Ivanov

    Examples of preflight requests, with credentials + PHP sample code. See: http://zinoui.com/blog/cross-domain-ajax-request