什么是JSONP,为什么创建它?

我理解JSON,但不理解JSONP。维基百科关于JSON的文档是JSONP的热门搜索结果。它说:

JSONP或“带填充的JSON”是一个JSON扩展,其中前缀被指定为调用本身的输入参数。

啊?什么调用?这对我来说没有任何意义。JSON是一种数据格式。没有调用。

第二次搜索结果来自一个叫雷米的人,他写了关于JSONP的文章:

JSONP是脚本标记注入,将服务器的响应传递给用户指定的函数。

我能理解这一点,但它仍然没有任何意义。


那么什么是JSONP?为什么创建它(它解决了什么问题)?我为什么要使用它?


增编:我刚刚在维基百科上创建了JSONP的新页面;它现在基于jvenema的答案对JSONP进行了清晰而彻底的描述。

577760 次浏览

因为您可以要求服务器为返回的JSON对象添加前缀。例如

function_prefix(json_object);

为了让浏览器eval“内联”JSON字符串作为表达式。此技巧使服务器可以直接在客户端浏览器中“注入”javascript代码,并绕过“同源”限制。

换句话说,您可以实现跨域数据交换


通常,XMLHttpRequest不允许直接跨域数据交换(需要通过同一域中的服务器),而:

<script src="some_other_domain/some_data.js&prefix=function_prefix>'可以从与源不同的域访问数据。


同样值得注意的是:即使服务器在尝试这种“技巧”之前应该被认为是“受信任的”,对象格式等可能变化的副作用也可以被包含。如果使用function_prefix(即适当的js函数)来接收JSON对象,则所述函数可以在接受/进一步处理返回的数据之前执行检查。

其实也没那么复杂…

假设您在域#0上,并且您想向域#1发出请求。为此,您需要跨域边界,在大多数浏览器中为不-不

绕过此限制的一个项目是<script>标记。当您使用脚本标记时,域限制会被忽略,但在正常情况下,您无法真正任何结果,脚本只会被评估。

输入#0。当您向启用了JSONP的服务器发出请求时,您会传递一个特殊参数,该参数会告诉服务器有关您的页面的一些信息。这样,服务器就能够以您的页面可以处理的方式很好地包装其响应。

例如,假设服务器期望一个名为#0的参数来启用其JSONP功能。那么你的请求如下所示:

http://www.example.net/sample.aspx?callback=mycallback

如果没有JSONP,这可能会返回一些基本的JavaScript对象,如下所示:

{ foo: 'bar' }

但是,使用JSONP,当服务器接收到“回调”参数时,它对结果的包装略有不同,返回如下所示:

mycallback({ foo: 'bar' });

如您所见,它现在将调用您指定的方法。因此,在您的页面中,您定义了回调函数:

mycallback = function(data){alert(data.foo);};

现在,当脚本加载时,它将被评估,您的函数将被执行。瞧,跨域请求!

同样值得注意的是JSONP的一个主要问题:你失去了对请求的很多控制。例如,没有“好”的方法来获取适当的失败代码。结果,你最终使用计时器来监控请求等,这总是有点可疑。JSONRequest的主张是允许跨域脚本、维护安全性和允许适当控制请求的好解决方案。

这些天(2015年),与JSONRequest相比,CORS是推荐的方法。JSONP对于较旧的浏览器支持仍然有用,但考虑到安全问题,除非您别无选择,否则CORS是更好的选择。

JSONP实际上是克服XMLHttpRequest相关文档相同域策略的简单技巧。(如您所知,不能将AJAX(XMLHttpRequest)请求发送到不同的域。)

所以,为了让js从另一个域获取数据,我们必须使用脚本超文本标记语言,而不是XMLHttpRequest相关文档

事实是-原来脚本标签可以以类似于XMLHttpRequest相关文档的方式使用!看看这个:

script = document.createElement('script');script.type = 'text/javascript';script.src = 'http://www.someWebApiServer.com/some-data';

加载数据后,您将获得一个脚本段,如下所示:

<script>{['some string 1', 'some data', 'whatever data']}</script>

然而,这有点不方便,因为我们必须从脚本标签获取这个数组。所以JSONP的创建者决定这会更好地工作(它是):

script = document.createElement('script');script.type = 'text/javascript';script.src = 'http://www.someWebApiServer.com/some-data?callback=my_callback';

注意到那边的my_callback函数吗?所以-当JSONP服务器收到您的请求并找到回调参数时-它将返回以下内容而不是返回普通的js数组:

my_callback({['some string 1', 'some data', 'whatever data']});

看看利润在哪里:现在我们得到了自动回调(my_callback),一旦我们得到数据就会触发它。
这就是关于JSONP的所有知识:它是一个回调和脚本标签。

注意:这些是JSONP使用的简单示例,这些不是生产就绪脚本。

基本JavaScript示例(使用JSONP的简单Twitter提要)

<html><head></head><body><div id = 'twitterFeed'></div><script>function myCallback(dataWeGotViaJsonp){var text = '';var len = dataWeGotViaJsonp.length;for(var i=0;i<len;i++){twitterEntry = dataWeGotViaJsonp[i];text += '<p><img src = "' + twitterEntry.user.profile_image_url_https +'"/>' + twitterEntry['text'] + '</p>'}document.getElementById('twitterFeed').innerHTML = text;}</script><script type="text/javascript" src="http://twitter.com/status/user_timeline/padraicb.json?count=10&callback=myCallback"></script></body></html>

基本jQuery示例(使用JSONP的简单Twitter提要)

<html><head><script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script><script>$(document).ready(function(){$.ajax({url: 'http://twitter.com/status/user_timeline/padraicb.json?count=10',dataType: 'jsonp',success: function(dataWeGotViaJsonp){var text = '';var len = dataWeGotViaJsonp.length;for(var i=0;i<len;i++){twitterEntry = dataWeGotViaJsonp[i];text += '<p><img src = "' + twitterEntry.user.profile_image_url_https +'"/>' + twitterEntry['text'] + '</p>'}$('#twitterFeed').html(text);}});})</script></head><body><div id = 'twitterFeed'></div></body></html>


JSONP代表带填充的JSON。(命名非常糟糕的技术,因为它与大多数人认为的“填充”无关。)

JSONP通过构造一个“脚本”元素(以超文本标记语言标记或通过JavaScript插入到DOM中)来工作,该元素向远程数据服务位置发出请求。响应是加载到浏览器上的javascript,其中包含预定义函数的名称以及传递的参数,即请求的JSON数据。当脚本执行时,该函数与JSON数据一起被调用,允许请求页面接收和处理数据。

进一步阅读访问:https://blogs.sap.com/2013/07/15/secret-behind-jsonp/

客户端代码片段

    <!DOCTYPE html><html lang="en"><head><title>AvLabz - CORS : The Secrets Behind JSONP </title><meta charset="UTF-8" /></head><body><input type="text" id="username" placeholder="Enter Your Name"/><button type="submit" onclick="sendRequest()"> Send Request to Server </button><script>"use strict";//Construct the script tag at Runtimefunction requestServerCall(url) {var head = document.head;var script = document.createElement("script");
script.setAttribute("src", url);head.appendChild(script);head.removeChild(script);}
//Predefined callback functionfunction jsonpCallback(data) {alert(data.message); // Response data from the server}
//Reference to the input fieldvar username = document.getElementById("username");
//Send Request to Serverfunction sendRequest() {// Edit with your Web Service URLrequestServerCall("http://localhost/PHP_Series/CORS/myService.php?callback=jsonpCallback&message="+username.value+"");}
</script></body></html>

服务器端的PHP代码

<?phpheader("Content-Type: application/javascript");$callback = $_GET["callback"];$message = $_GET["message"]." you got a response from server yipeee!!!";$jsonResponse = "{\"message\":\"" . $message . "\"}";echo $callback . "(" . $jsonResponse . ")";?>

JSONP是解决跨域脚本错误的好方法。您可以纯粹使用JS使用JSONP服务,而无需在服务器端实现AJAX代理。

您可以使用b1t.co服务来查看它是如何工作的。这是一项免费的JSONP服务,允许您缩小URL。这是用于该服务的URL:

http://b1t.co/Site/api/External/MakeUrlWithGet?callback=

例如,调用http://b1t.co/Site/api/External/MakeUrlWithGet?callback=whateverJavascriptName&; url=google.com

将返回

whateverJavascriptName({"success":true,"url":"http://google.com","shortUrl":"http://b1t.co/54"});

因此,当get作为src加载到您的js中时,它将自动运行您应该实现为回调函数的任何JavascriptName:

function minifyResultsCallBack(data){document.getElementById("results").innerHTML = JSON.stringify(data);}

要实际进行JSONP调用,您可以通过多种方式(包括使用jQuery)进行,但这里有一个纯JS示例:

function minify(urlToMinify){url = escape(urlToMinify);var s = document.createElement('script');s.id = 'dynScript';s.type='text/javascript';s.src = "http://b1t.co/Site/api/External/MakeUrlWithGet?callback=resultsCallBack&url=" + url;document.getElementsByTagName('head')[0].appendChild(s);}

一个分步示例和一个要练习的jsonp Web服务可在以下位置获得:这篇文章

一个使用JSONP的简单示例。

client.html

    <html><head></head>body>

<input type="button" id="001" onclick=gO("getCompany") value="Company"  /><input type="button" id="002" onclick=gO("getPosition") value="Position"/><h3><div id="101">
</div></h3>
<script type="text/javascript">
var elem=document.getElementById("101");
function gO(callback){
script = document.createElement('script');script.type = 'text/javascript';script.src = 'http://localhost/test/server.php?callback='+callback;elem.appendChild(script);elem.removeChild(script);

}
function getCompany(data){
var message="The company you work for is "+data.company +"<img src='"+data.image+"'/   >";elem.innerHTML=message;}
function getPosition(data){var message="The position you are offered is "+data.position;elem.innerHTML=message;}</script></body></html>

server.php

  <?php
$callback=$_GET["callback"];echo $callback;
if($callback=='getCompany')$response="({\"company\":\"Google\",\"image\":\"xyz.jpg\"})";
else$response="({\"position\":\"Development Intern\"})";echo $response;
?>

在了解JSONP之前,您需要了解JSON格式和XML。目前Web上最常用的数据格式是XML,但XML非常复杂。它使用户不方便处理嵌入在网页中的内容。

为了使JavaScript可以方便地交换数据,甚至作为数据处理程序,我们根据JavaScript对象的措辞,开发了一种简单的数据交换格式,即JSON。JSON可以用作数据,也可以用作JavaScript程序。

JSON可以直接嵌入到JavaScript中,使用它们可以直接执行某些JSON程序,但由于安全限制,浏览器沙盒机制禁用跨域JSON代码执行。

为了使JSON可以在执行后传递,我们开发了一个JSONP。JSONP使用JavaScript Callback功能和标签绕过浏览器的安全限制。

简而言之,它解释了JSONP是什么,它解决了什么问题(何时使用它)。

JSONP代表JSON填充

这是网站,很好的例子用最简单的解释这种技术到最先进在平面JavaScript中:

w3schools.com/JSONP

上面描述的我最喜欢的技术之一是动态JSON结果,其中允许在URL参数中将JSON发送到PHP文件,让PHP文件还根据获取的信息返回一个JSON对象

jQuery也有使用JSONP的工具这样的工具:

jQuery.ajax({url: "https://data.acgov.org/resource/k9se-aps6.json?city=Berkeley",jsonp: "callbackName",dataType: "jsonp"}).done(response => console.log(response));

这是我的ELI5(像我5一样解释我)为那些需要它的人尝试。

太长别读

JSONP是为了绕过Web浏览器中的安全限制而发明的老把戏,该限制禁止我们获取与我们自己不同的网站/服务器(称为不同的起源1)中的数据。

该技巧通过使用<script>标记从其他地方加载JSON(例如:{ "city":"Barcelona" })来工作,这将向我们发送包装在函数实际JSONP(“带有填充的JSON”)中的数据:

tourismJSONP({"city":"Barcelona"})

以这种方式接收它使我们能够使用tourismJSONP函数中的数据。JSONP是一种不好的做法不再需要,不要使用它(在最后阅读)。


问题

假设我们想在ourweb.com上使用托管在anotherweb.com的一些JSON数据(或任何真正的原始数据)。如果我们使用GET请求(想想XMLHttpRequest,或fetch调用,$.ajax等),我们的浏览器会告诉我们不允许出现这个丑陋的错误:

ChromeCORS控制台错误

这是一个内容安全策略限制错误,它旨在保护用户免受某些攻击。您应该正确配置它(参见末尾)。

JSONP技巧在这里对我们有什么帮助?好吧,<script>标签不受整个服务器(起源1)限制!这就是为什么我们可以从任何服务器加载像jQuery或Google地图这样的库。

重要的一点是:如果你仔细想想,这些库是实际的、可运行的JS代码(通常是一个包含所有逻辑的大型函数)。但是原始数据不是代码。没有什么可运行的;它只是纯文本。

因此,浏览器将下载<script>标记指向的数据,并且在处理时它会理所当然地抱怨:

WTF是我们加载的{"city":"Barcelona"}垃圾吗?这不是代码。我不会计算!


旧的JSONP黑客

如果我们能以某种方式制作纯文本可运行,我们就可以在运行时获取它。我们需要anotherweb.com将其作为代码发送,所以当它下载时浏览器会运行它。我们只需要两件事:1)以一种可以运行的方式获取数据,2)在客户端编写一些代码,这样当数据运行时,我们的函数就会被调用,我们就可以使用这些数据。

对于1),如果外部服务器对JSONP友好,我们将要求这样的数据:

<script src="https://anotherweb.com/api/tourism-data.json?myCallback=tourismJSONP"></script>

所以我们会像这样收到它:

tourismJSONP({"city":"Barcelona"})

现在我们可以与之交互的js代码

根据2),我们需要在代码中编写一个同名函数,如下所示:

function tourismJSONP(data){alert(data.city); // "Barcelona"}

浏览器将下载JSONP并运行它,它调用我们的函数,其中参数data将是anotherweb.com中的JSON数据。我们现在可以随心所欲地处理我们的数据。


不要使用JSONP,使用CORS

JSONP是一个跨站点的黑客有几个缺点:

  • 我们只能执行GET请求
  • 由于这是一个由简单脚本标记触发的GET请求,我们没有得到有用的错误或进度信息
  • 还有一些安全问题,例如在您的客户端JS代码中运行可能会更改为恶意有效负载
  • 它只解决了JSON数据的问题,但同源安全策略适用于其他数据(WebFonts、使用DrawImage()绘制的图像/视频…)
  • 它既不优雅也不易读。

问题是这里有现在没必要用了

你应该阅读关于CORS这里,但它的要点是:

跨源资源共享(CORS)是一种使用额外的HTTP标头告诉浏览器提供Web应用程序在一个原点运行,从不同的位置访问选定的资源起源。Web应用程序在执行跨源HTTP请求时请求具有不同来源(域、协议或#36825;自己的



  1. 起源由3件事定义:协议端口主机。所以,https://web.comhttp://web.com(不同的协议)、https://web.com:8081(不同的端口)和显然https://thatotherweb.net(不同的主机)是不同的起源

背景

您应该尽可能使用CORS(即您的服务器或API支持它,浏览器支持就足够了),因为JSONP存在固有的安全风险。

示例

JSONP(带填充的JSON)是一种常用于绕过Web浏览器中的跨域策略。(您不允许向浏览器认为位于不同服务器上的网页发出AJAX请求。)

JSON和JSONP在客户端和服务器上的行为不同。JSONP请求不会使用XMLHTTPRequest和相关的浏览器方法分派。相反,会创建一个<script>标签,其源设置为目标URL。然后将此脚本标签添加到DOM(通常在<head>元素中)。

JSON请求:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {// success};};
xhr.open("GET", "somewhere.php", true);xhr.send();

JSONP请求:

var tag = document.createElement("script");tag.src = 'somewhere_else.php?callback=foo';  
document.getElementsByTagName("head")[0].appendChild(tag);

JSON响应和JSONP响应之间的区别在于JSONP响应对象作为参数传递给回调函数。

JSON:

 { "bar": "baz" }

JSONP:

foo( { "bar": "baz" } );

这就是为什么您会看到包含callback参数的JSONP请求,以便服务器知道包装响应的函数的名称。

这个函数<强>必须存在在全局作用域当时中,<script>标签由浏览器评估(一旦请求完成)。

处理JSON响应和JSONP响应之间需要注意的另一个区别是,JSON响应中的任何解析错误都可以通过包装评估响应文本的尝试来捕获由于JSONP响应的性质,响应中的解析错误将导致无法捕获的JavaScript解析错误。

这两种格式都可以通过在发起请求之前设置超时并在响应处理程序中清除超时来实现超时错误。

使用jQuery

使用jQuery进行JSONP请求的有用性是jQuery在后台为您做所有的工作

默认情况下,jQuery要求您在AJAX请求的URL中包含&callback=?。jQuery将采用您指定的success函数,为其分配一个唯一名称,并将其发布在全局范围内。然后它将用它分配的名称替换&callback=?中的问号?

比较类似的JSON和JSONP实现

以下假设响应对象{ "bar" : "baz" }

JSON:

   var xhr = new XMLHttpRequest();    
xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {document.getElementById("output").innerHTML = eval('(' + this.responseText + ')').bar;};};    
xhr.open("GET", "somewhere.php", true);xhr.send();

JSONP:

 function foo(response) {document.getElementById("output").innerHTML = response.bar;};    
var tag = document.createElement("script");tag.src = 'somewhere_else.php?callback=foo';    
document.getElementsByTagName("head")[0].appendChild(tag);