65.9K
CodeProject 正在变化。 阅读更多。
Home

JSON 转 JSONP:绕过同源策略

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (9投票s)

2009 年 9 月 25 日

CPOL

9分钟阅读

viewsIcon

102470

downloadIcon

1171

本文展示了如何使用带填充的 JavaScript 对象表示法 (JSONP) 从不同域获取数据,绕过同源策略。

引言

在我上一篇文章(准备 JSON Web 服务并使用 JQuery 访问)中,我解释了 JSON、如何从 Web 服务返回 JSON 数据以及如何使用 JQuery 启用 JSON 的 Web 服务。在本文中,我将解释如何使用带填充的 JavaScript 对象表示法 (JSONP),这是一个从 JSON 扩展而来的概念,可以用来解决跨域问题。

什么是 JSONP,为什么要用它?

AJAX 是 Web 2.0 中的一项关键技术,在网站中被广泛使用。AJAX 使用 XMLHttpRequest 客户端 API 在后台与服务器通信。然而,由于安全原因,这种方法不允许跨域通信。带填充的 JavaScript 对象表示法 (JSONP) 是一种从外部域获取 JSON 数据的方法。它是获取外部域数据的其他方法(Web 代理和 IFrame)的更好、更简洁的替代方案。

同源策略

同源策略是浏览器端编程语言(如 JavaScript)中的一个概念,它允许访问同一站点(相同域)中的资源,但阻止访问不同域中的资源。为了克服同源策略,我们手头有两种流行的选择:

使用代理 Web 服务

由于 XMLHttpRequest(在 AJAX 中)不允许跨域调用,常见的做法是使用代理 Web 服务来访问第三方数据。假设您的网站托管在 www.mydomain.com,您需要访问来自不同域 www.thirdpartydomain.com 的数据。使用 AJAX,您无法直接调用 www.thirdpartydomain.com 上的 Web 服务,但您可以在您的域中编写一个 Web 服务,该服务将从 www.thirdpartydomain.com 获取数据。该方法如图 1 所示:

Fig_1_-_Proxy_to_access_third_party_domain_data.jpg

图 1:使用代理 Web 服务从不同域访问数据

这种方法之所以奏效,是因为 XMLHttpRequest 调用了我自己域中的 Web 服务(即代理 Web 服务),然后代理 Web 服务调用了不同域中的实际 Web 服务。但尽管这是最简单、最广泛使用的解决方案,它需要两次 Web 服务调用,这会更慢。此外,每次对外部 Web 服务的调用都需要通过我的 Web 服务,这会占用我的服务器宝贵的线程来处理。

使用 IFrame

使用 IFrame,我们可以轻松地从第三方站点获取数据。IFrame 易于使用但难以管理,因为每个 IFrame 都是页面中的一个独立元素,并且 IFrames 之间的交互很困难。此外,一旦 IFrame 中的内容加载,内容本身也会受到同源策略的限制。

我们为什么需要打破同源策略?

今天的 Web 应用程序在数据和 UI 方面都非常丰富,从技术角度来看很复杂,并且结合了来自不同来源的不同数据集。例如,您有一个大学生访问的大学网站。此外,您还为此大学创建了一个 Facebook 群组,并且您希望在您的大学网站上显示群组活动。为了从 Facebook 获取活动,您需要从您的网页访问 Facebook 网站上的服务(一个不同的域)。这就是跨域问题出现的地方。您可以从服务器端代码访问 Facebook 网站服务,而不会出现任何跨域问题。但这会阻止页面处理完成,直到 Web 服务调用完成。您可以添加一个 JavaScript 块来从客户端浏览器调用 Web 服务,而不是从服务器端代码调用 Web 服务,从而减轻服务器的负载。在 Mashup 应用程序中,我们需要访问来自不同来源的数据。在计算世界中,Mashup 是一种 Web 应用程序,它结合了来自两个或多个外部源的数据或功能。

JSONP 如何工作?

同源策略不允许从一个域加载的脚本操作从不同域加载的文档属性。浏览器这样做是为了隔离来自不同域的内容,以防止不当操作。但是,同源策略不阻止从不同域动态添加脚本到页面,只要该脚本不尝试从不同域加载文档。JSONP 是这些功能的组合:“按需 JavaScript”和“同源策略对从不同域添加 JavaScript 的灵活性”。

按需 JavaScript:这允许在页面加载后向页面添加 JavaScript。可以通过使用 AJAX/XMLHttpRequest 调用 Web 服务来添加 JavaScript。因此,由于这种按需 JavaScript 功能,JavaScript 可以在页面加载后添加。脚本将在添加到页面后立即执行。有关按需 JavaScript 的详细信息可以在此处找到。

为了解释 JSONP 的工作原理,假设有一个外部第三方网站 www.thirdpartydomain.com,我们有我们的网站 www.mydomain.com,我们的网站将调用第三方网站的 Web 服务来获取数据。让我们考虑以下场景来解释 JSONP:

  • 在页面中添加一个 JS 函数:假设在您的页面中,您有一个 JavaScript 函数 showThirdpartyData,它接受一个参数(JSON 类型)并处理该参数以在您的网站页面上显示数据。但是,showThirdPartyData 期望的参数 (以下代码片段中的 dataArgument) 将来自不同域 www.thirdpartydomain.com 上的 Web 服务。以下代码片段显示了函数签名:
  • <script type="text/javascript">
            function showThirdPartyData(dataArgument) { 
                //process data here
            }
    </script>
  • 确保第三方 Web 服务支持 JSONP:我们需要在 www.thirdpartydomain.com 上有一个支持 JSONP 的 Web 服务。也就是说,Web 服务将返回 JSON 数据,但应包装在一个函数名中。Web 服务将返回的 JSON 数据将如下所示:
  • showThirdPartyData(‘{ firstName: 'Sohel', lastName: 'Rana' }’);

    因此,通过调用 www.thirdpartydomain.com 上的 Web 服务,我们将生成如上所示的 JSON 数据。调用 Web 服务将生成 JSON 数据,这实际上是一个 JavaScript 函数调用。函数名(数据将包装在其中)需要可配置。例如,我们可以在 Web 服务调用中通过查询字符串传递函数名,Web 服务将数据包装在函数名中。Flickr API 就是这样工作的,我们稍后会看到。

  • 调用第三方 Web 服务:现在您可以使用 AJAX 调用 www.thirdpartydomain.com Web 服务,因为它生成一个 JavaScript 函数调用,并且该 JavaScript 函数不尝试从 www.thirdparty.com 以外的不同域访问数据。因此,调用此 Web 服务符合同源策略 [同源策略不阻止从不同域动态添加脚本到页面,直到该脚本不尝试从不同域加载文档]。以下代码块可用于从 www.thirdpartydomain.com 站点调用 Web 服务。您可以在按钮的 OnClick 事件上调用以下 JavaScript 函数 (callWebService):
  • function callWebService() {
         // Insert dynamic script
         var script = document.createElement('script');
         script.src = 'http://www.thirdpartydomain.com/webservice/... ';
         
         // append the script in the document body. 
         // As per on-deman script behaviour as soon as you add the script to 
         // the document,the script will be execute and the web service will 
         // be called.
         document.body.appendChild(script); 
    }

以下是 JSONP 的工作顺序:

  1. callWebService 方法将在页面中添加一个脚本标签,并将脚本标签的源设置为第三方 Web 服务的 URL。
  2. 您将在某些事件(例如按钮的点击事件)上调用 CallWebService 方法。一旦调用 callWebService 方法,来自第三方站点的脚本将添加到页面中。
  3. 一旦脚本标签添加到页面中,脚本将按照按需 JavaScript 的行为执行。脚本执行后,页面中的 showThirdPartyData 方法将被调用。

Web 服务如何兼容 JSONP

为了使 JSONP 工作,来自 www.thirdpartydomain.com 的 Web 服务将支持 JSONP。为了支持 JSONP,Web 服务必须以以下格式返回 JSON 数据:

functionName(‘{JSONData}’)

这里的函数名是一个 JavaScript 函数名。因此,当您从另一个站点调用 Web 服务时,Web 服务将生成 JSON 数据,这实际上是一个 JavaScript 函数调用。例如,要从 Flickr 获取标记为“cat”的最新图像,我们可以调用 Web 服务:http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=myFunctionName

在上面的 URL 中,查询字符串 tags=cat 告诉服务返回标记为 cat 的图像。这里,重要的是 jsoncallback=myFunctionName。这告诉服务返回数据将包装在名为 myFunctionName 的函数调用中。如果您访问该 URL,您将获得如下所示的 JSON 数据:

Fig_2_-_JSONP_support_in_flickr_API.jpg

图 2:Flickr API 中的 JSONP 支持

因此,上面对 Flickr API 的调用在查询字符串 jsoncallback 中获取函数名,我们可以传递我们感兴趣使用的方法名。

一个使用 Flickr 的真实示例

在本文提供的代码中,我使用了 Flickr API 来显示最近的图片。使用 JSONP,我从 flickr.com 访问了数据,这与我的域不同。Web 服务 URL 是:http://api.flickr.com/services/feeds/photos_public.gne?tags=dog&tagmode=any&format=json&jsoncallback=showFilckrDataDog

当您运行此代码随附的应用程序时,您将在网页中获得两个按钮。单击一个按钮将显示标记为 cat 的图像,单击另一个按钮将显示标记为 dog 的图像。这两个按钮调用两个不同的 JavaScript 函数:getFlickrDataWithDogTaggetFlickrDataWithCatTaggetFlickrDataWithDogTag 使用原生 JavaScript 方法向页面添加脚本。而 getFlickrDataWithCatTag 使用 JQuery 调用支持 JSONP 的 Web 服务。JQuery.getJSON 方法允许从不同域加载 JSON 数据。getJSON 方法的格式是:

jQuery.getJSON(url, data, callback)

url 是 Web 服务的 URL。data(发送到 Web 服务的 JSON 数据)如果不需要可以省略。callback 是回调方法,一旦 Web 服务返回 JSON 数据就会被调用。请记住,当您使用 getJSON 方法调用 Web 服务时,Web 服务 URL 会略有不同。jsoncallback 查询字符串需要有一个“?”值。getJSON 方法中使用的 URL 如下所示:

$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?" + 
          "tags=cat&tagmode=any&format=json&jsoncallback=?", showFilckrDataCat);

jQuery 将自动用回调方法(在上面的 URL 中,这是 showFlickrDataCat)替换“?”。此外,jQuery 会为您添加脚本标签。因此,当 Web 服务调用返回时,jQuery 将添加脚本标签,该标签将调用您页面中的 JavaScript 方法。

参考文献

您可以在以下 URL 中获取有关 JSONP 的更多信息:

结论

对于 Mashup 等应用程序,我们需要组合来自不同来源的数据,这些来源可能不在同一域中。同源策略禁止访问来自其他域的数据。我们有几种选择可以绕过同源策略。一种解决方案是代理 Web 服务,它会带来不必要的处理,而每个独立的 IFrame 都有同源策略限制。JSONP 为其他两种方法提供了一个更好的替代方案。但是,我们需要在另一端有支持 JSOP 的 Web 服务才能使用 JSONP 消费数据。

© . All rights reserved.