JSONP是什么,为什么会被创建?

2448
我了解JSON,但不了解JSONP。Wikipedia关于JSON是(曾经是)JSONP的最高搜索结果。它说:
“JSONP或“带填充的JSON”是一种JSON扩展,其中前缀被指定为调用本身的输入参数。”
什么调用?这对我没有任何意义。JSON是一种数据格式。没有调用。 第二个搜索结果来自一个叫Remy的人,他写了关于JSONP的内容:
“JSONP是脚本标记注入,将服务器响应传递给用户指定的函数。”
我有点理解,但还是不太明白。

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


附录: 我刚刚在维基百科上创建了一个关于JSONP的新页面;它现在有一个清晰和详尽的JSONP描述,基于jvenema的回答。


45
请注意,如果你不完全信任所连接的服务器,请勿使用JSONP。如果服务器被攻击,你的网页很容易受到攻击。 - ninjagecko
10
请注意,如果未正确实施,JSONP可能会被劫持。 - Pacerier
4
我想要给JSONP的作者致谢,他阐述了这项技术背后的哲学:Bob Ippolito关于JSONP的归档。他将JSONP介绍为“一种新的、技术不可知的标准方法,用于跨域数据获取的脚本标签方法”。 - gawkface
5
对于那些十年后因为搜寻其他内容而来到这里并感到困惑的人:JSON-P现在也是处理JSON的Java API的名称,主要用于解析和编写JSON。它类似于XML StAX和DOM API,用于流式输入/输出和文档建模。它支持JSON Pointer进行查询,就像XPath用于XML一样。我认为它还打算通过JSON Patch提供转换JSON的手段,就像XSLT和Java XML Transformer API处理XML一样,但它仍然比XSLT不够先进。这个问题是关于脚本注入的。 - G_H
10个回答

2290

实际上并不太复杂...

假设您在域名为example.com的网站上,想要向域名为example.net的网站发送请求。要这样做,您需要越过跨域限制,这在大多数浏览器中是不允许的

唯一可以绕过此限制的是<script>标签。当您使用一个


224
请注意,使用JSONP存在一些安全隐患。由于JSONP实际上是JavaScript,它可以执行JavaScript所能执行的所有操作,因此您需要相信JSONP数据提供者。我在这里写了一篇博客文章详细讨论了这个问题:http://erlend.oftedal.no/blog/?blogid=97 - Erlend
79
JSONP相对于使用<script>标签,是否存在任何新的安全隐患?使用<script>标签时,浏览器会隐式信任服务器传送的非有害JavaScript代码,并盲目地对其进行评估。JSONP是否改变了这一事实呢?看起来并没有改变。 - Cheeso
27
不,它不会。如果你信任它可以传递JavaScript,那么对于JSONP也是同样适用的。 - Jerod Venema
18
值得注意的是,您可以通过更改数据返回方式来稍微增强安全性。如果以真正的JSON格式返回脚本,例如mycallback('{"foo":"bar"}')(请注意,参数现在是字符串),则可以在评估之前手动解析数据以进行“清理”。 - Jerod Venema
10
CURL是一种服务器端解决方案,而非客户端。它们有着不同的用途。 - Jerod Venema
显示剩余14条评论

796

JSONP 是一个简单的技巧,可以绕过 XMLHttpRequest 同源策略。 (如你所知,无法向不同域发送 AJAX (XMLHttpRequest) 请求。)

因此 - 我们需要使用 script HTML 标签来代替 XMLHttpRequest,通常情况下会使用这些标签来加载 js 文件,以便 js 可以从另一个域获取数据。听起来奇怪吗?

事实是 - 原来可以使用类似于 XMLHttpRequest 的方式来使用 script 标签!看看这个:

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>

然而,这有点不方便,因为我们必须从 script 标签中获取这个数组。因此,JSONP 的创建者决定使用以下方法会更好(并且确实如此):

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

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

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

看看利润在哪里:现在我们得到了自动回调(my_callback),一旦我们获得数据就会触发它。
这就是关于JSONP需要知道的全部:它是一个回调和脚本标记。

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

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

<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 feed)

<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>


JSONPJSON with Padding 的缩写(这个命名有些不准确,因为它与大多数人所想的“padding”没有任何关系)。


41
感谢对 script 标记的解释。我之前无法理解 JSONP 如何绕过跨域安全策略。经过你的解释,我感到有点儿愚蠢错过了这一点… - Eduard
15
这是对jvenema回答的很好的补充 - 在你指出json数据必须通过脚本元素访问之前,我不明白为什么需要回调。 - Matt
8
感谢您提供如此清晰易懂的解释。我希望我的大学教科书也能够由像您这样的人编写 :) - hashbrown
1
很好的解释,比之前的更好。当然,你的摘录“通常用于加载js文件,以便js从另一个域获取数据。听起来很奇怪?”对我也是一个启示。示例代码非常详细。 - SIslam
我喜欢这个带有具体例子的解释,比被接受的答案更好!谢谢! - ihor.eth
为什么这不是一个被接受的答案?当前被接受的答案没有意义,因为它只提到了 <script> 而没有说明如何使用它... - vanowm

64
这是一个非常简单易懂的解释,适合那些需要帮助的人。
简而言之,JSONP是一种旧的技巧,用于绕过Web浏览器中的安全限制,该限制禁止我们获取不同网站/服务器(称为不同来源)上的数据。
这个技巧通过使用

9
伙计,这个脚注对批准的答案提供了100%的清晰度!谢谢你... - M'Baku
1
JSONP非常适用于本地开发,因为许多浏览器仍然以严格的方式实现CORS策略。例如,大多数浏览器将允许您的脚本向CORS域发送XMLHttpRequest请求,但是除非使用安全连接(HTTPS),否则无法交换cookie数据。现在,在没有SSL证书的情况下,您无法在本地开发中使用HTTPS。即使您生成了自签名的SSL证书,浏览器仍会阻止它,因为它未经CA签名。现在,要通过CA进行签名,您的域必须在Internet上可访问,以便可以进行验证。请停一停吧。JSONP就是我的最爱! - Udo E.
只是为了补充另一个缺点,每次生成唯一回调函数的JSONP请求都无法被缓存,因为查询字符串总是不同的。 - Sean H

50
JSONP通过构造“script”元素(可以在HTML标记中或通过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 Runtime
    function requestServerCall(url) {
      var head = document.head;
      var script = document.createElement("script");

      script.setAttribute("src", url);
      head.appendChild(script);
      head.removeChild(script);
    }

    //Predefined callback function    
    function jsonpCallback(data) {
      alert(data.message); // Response data from the server
    }

    //Reference to the input field
    var username = document.getElementById("username");

    //Send Request to Server
    function sendRequest() {
      // Edit with your Web Service URL
      requestServerCall("http://localhost/PHP_Series/CORS/myService.php?callback=jsonpCallback&message="+username.value+"");
    }    

  </script>
   </body>
   </html>

服务器端的 PHP 代码

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

3
顶部的链接现在只是404错误。 - Kevin Beal
该链接的内容现在可以在http://scn.sap.com/community/developer-center/front-end/blog/2013/07/15/secret-behind-jsonp上获取。 - ᴠɪɴᴄᴇɴᴛ

44
因为您可以要求服务器在返回的JSON对象前置一个前缀。例如:function_prefix(json_object);,这样浏览器就可以将JSON字符串"内联"作为表达式进行eval。这个技巧使得服务器能够直接在客户端浏览器中“注入”JavaScript代码,并且越过了“同源策略”,从而实现跨域数据交换。换句话说,您可以实现跨域数据交换。
通常情况下,XMLHttpRequest不允许直接进行跨域数据交换(必须通过同一域内的服务器),但是:在以下情况下,即使来自不同域名,也可以通过

2
“添加前缀”很令人困惑 :) - jub0bs
不确定包含后果的警告是准确的。恶意服务器可以返回 function_prefix();super_dangerous_function{window.open('youvebeenhacked!')}() - apod

18

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

您可以使用 b1t.co 服务来了解它的工作原理。这是一个免费的JSONP服务,可以让您缩短URL。以下是要使用该服务的URL:

http://b1t.co/Site/api/External/MakeUrlWithGet?callback=[resultsCallBack]&url=[escapedUrlToMinify]

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

将返回:

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

因此,当它作为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网络服务进行练习。


3
感谢您发布回答!请注意,您应该在这个网站上发布答案的关键部分,否则您的帖子可能会被删除[请参阅FAQ中提到的“几乎仅仅是链接”的答案]。您仍然可以包含链接作为“参考”,但答案本身应该能够独立存在而不需要链接。 - Taryn

15
一个使用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;

    ?>    

7
在了解JSONP之前,您需要了解JSON格式和XML。目前在Web上最常用的数据格式是XML,但XML非常复杂。它使用户难以处理嵌入在网页中的内容。
为了使JavaScript能够轻松交换数据,甚至作为数据处理程序,我们使用JavaScript对象的措辞并开发了一个简单的数据交换格式,即JSON。JSON可以用作数据或JavaScript程序。
JSON可以直接嵌入JavaScript中,使用它们可以直接执行某些JSON程序,但由于安全限制,浏览器沙箱机制禁用跨域JSON代码执行。
为了使JSON在执行后可以传递,我们开发了JSONP。JSONP通过JavaScript回调功能和

5
我对此进行了负评,因为我不相信XML是2015年12月在网络上使用最广泛的数据格式这个说法。 - RobbyD
2
这仍然没有回答为什么要使用 JSONP 而不是 JSON。所有这些安全限制是从哪里来的?为什么我们可以使用 JSONP,但不能使用 JSON 进行跨域? - Merunas Grincalaitis

3

JSONP代表带有填充的JSON

这是一个网站,其中包含很棒的例子,从最简单的使用到最高级的平面JavaScript技术的解释:

w3schools.com / JSONP

我更喜欢上面描述的一种技术动态JSON结果,它允许将JSON发送到URL参数中的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)
);

1

背景

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

示例

JSONP(带填充的 JSON)是一种常用的方法,用于绕过 Web 浏览器中的跨域策略。(您不被允许向被浏览器视为位于不同服务器上的 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" } );

这就是为什么你会看到JSONP请求包含callback参数,以便服务器知道将响应包装在哪个函数中。

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

JSON响应和JSONP响应处理之间要注意的另一个区别是,JSON响应中的任何解析错误都可以通过在try/catch语句中包装尝试评估responseText来捕获。由于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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接