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


2128

我了解JSON,但不了解JSONP。Wikipedia上有关JSON的文档是JSONP的最高搜索结果。它说:

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

??什么电话 这对我来说毫无意义。JSON是一种数据格式。没有电话

第二个搜索结果是由某些人叫雷米,谁写的这个约JSONP:

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

我可以理解,但这仍然没有任何意义。


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


附录:我刚刚在Wikipedia 上为JSONP创建了一个新页面。根据jvenema的回答,它现在对JSONP有了清晰而透彻的描述。


29
作为记录,如果您不信任与100%通信的服务器,请不要使用JSONP。如果遭到入侵,您的网页将受到严重损害。
ninjagecko 2012年

7
另请注意,如果实施不正确,则可以劫持 JSONP 。
Pacerier

3
我想感谢JSONP的作者,他提供了背后的哲学:Bob Ippolito在JSONP上的档案。他将JSONP介绍为“跨域数据获取的脚本标签方法的新技术不可知标准方法”。
roughvchawla

Answers:


2047

实际上并不太复杂...

假设您正在使用域example.com,并且想向域发出请求example.net。要做到这一点,你需要跨域边界,一个没有没有在大多数browserland的。

绕过此限制的一项是<script>标签。使用脚本标记时,将忽略域限制,但是在正常情况下,您实际上无法对结果执行任何操作,只是对脚本进行了评估。

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

例如,假设服务器需要一个名为的参数callback来启用其JSONP功能。然后您的请求将如下所示:

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

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

{ foo: 'bar' }

但是,使用JSONP时,服务器收到“ callback”参数时,其包装结果会有所不同,返回如下所示:

mycallback({ foo: 'bar' });

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

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

现在,加载脚本后,将对其进行评估,然后将执行您的函数。瞧,跨网域要求!

值得注意的是JSONP的一个主要问题:您失去了对请求的大量控制。例如,没有“不错”的方法来找回正确的故障代码。结果,您最终会使用计时器来监视请求等,这总是让人怀疑。JSONRequest的主张是一个很好的解决方案,它允许跨域脚本编写,维护安全性并允许对请求的适当控制。

如今(2015年),CORS是推荐的方法,而不是JSONRequest。JSONP对于较旧的浏览器支持仍然有用,但是考虑到安全隐患,除非您别无选择,否则CORS是更好的选择。


180
请注意,使用JSONP有一些安全隐患。由于JSONP确实是javascript,因此它可以完成javascript可以做的所有其他事情,因此您需要信任JSONP数据的提供者。我在这里撰写了有关此内容的som博客文章:erlend.oftedal.no/blog/?blogid=97
Erlend

72
JSONP中是否真的存在<script>标记中没有的任何新安全含义?使用脚本标签,浏览器将隐式地信任服务器以提供无害的Javascript,浏览器会盲目地对其进行评估。JSONP是否会改变这一事实?似乎没有。
Cheeso 2010年

23
不,不是。如果您相信它可以交付javascript,则JSONP同样适用。
jvenema 2010年

15
值得注意的是,您可以通过更改返回数据的方式来提高安全性。如果您以真实的JSON格式(例如mycallback('{“ foo”:“ bar”}'))返回脚本(请注意,该参数现在是字符串),那么您可以自己手动解析数据以在“清理”之前评估。
jvenema 2010年

8
CURL是服务器端的解决方案,而不是客户端的解决方案。它们有两个不同的目的。
jvenema 2010年

712

JSONP实际上是克服XMLHttpRequest相同域策略的简单技巧。(您知道不能将AJAX(XMLHttpRequest)请求发送到另一域。)

因此- 为了代替js 使用XMLHttpRequest,我们必须使用脚本 HTML标记(通常用于加载js文件的HTML标记),以便js从另一个域获取数据。听起来怪怪的?

事实是- 可以以类似于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>

但是,这有点不方便,因为我们必须从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 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>


JSONP表示带有Padding的JSON。(非常糟糕的技术,因为它实际上与大多数人认为的“填充”无关。)


34
感谢您提供脚本标签说明。我无法弄清楚JSONP如何绕过了跨域安全策略。经过解释后,我感到有点愚蠢,没能指出要点...
爱德华

13
这是对jvenema答案的非常好的补充答案-我不明白为什么要进行回调,直到您指出否则必须通过script元素访问json数据。
马特

5
感谢您这样清晰的解释。我希望我的大学教科书是由像你这样的人写的:)
hashbrown 2014年

1
很好的解释,而不是前一个。当然,您的摘录“通常用于加载js文件的文件,以便js从另一个域获取数据。听起来很奇怪吗?” 也让我大开眼界。示例代码非常出色。
SIslam

填充不必一定是文字。这是一个隐喻。因此它的意思是“带有某些“空格”的JSON”。哈哈
marvinIsSacul

48

JSONP通过构造一个“脚本”元素(以HTML标记或通过JavaScript插入DOM)来工作,该元素请求到远程数据服务位置。响应是使用预定义函数名称以及所传递的参数(即请求的JSON数据)加载到浏览器上的JavaScript。执行脚本时,该函数将与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
现在顶部的链接只有404s
Kevin Beal 2014年


42

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

function_prefix(json_object);

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

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


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

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


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


“添加前缀”令人困惑:)
jub0bs

19

JSONP可以很好地解决跨域脚本错误。您可以只使用JS使用JSONP服务,而不必在服务器端实现AJAX代理。

您可以使用b1t.co服务查看其工作方式。这是一项免费的JSONP服务,可让您缩小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"});

因此,当将该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服务进行练习:


2
感谢您发布答案!请注意,您应该在此处,本网站上发布答案的重要部分,否则您的帖子可能会被删除。请参阅FAQ,其中提到的答案“远远超过链接”。如果愿意,您仍可以包括该链接,但仅作为“参考”。答案应该独立存在,不需要链接。
塔林

14

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

    ?>    

8

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

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

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

为了使JSON在执行后可以传递,我们开发了JSONP。JSONP通过JavaScript回调功能和<script>标签绕过了浏览器的安全限制。

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


4
我对此表示反对,因为我不相信XML是15年12月网络上最常用的dat格式的说法。
RobbyD

仍然不知道为什么使用jsonp代替json。所有这些安全限制来自何处?为什么我们可以将jsonp而不是json用于跨域?
Merunas Grincalaitis

4

已经给出了很好的答案,我只需要以javascript代码块的形式给出我的作品(我还将为跨域请求提供更现代,更好的解决方案:带有HTTP标头的CORS):

JSONP:

1. client_jsonp.js

$.ajax({
    url: "http://api_test_server.proudlygeek.c9.io/?callback=?",
    dataType: "jsonp",
    success: function(data) {
        console.log(data);    
    }
});​​​​​​​​​​​​​​​​​​

2. server_jsonp.js

var http = require("http"),
    url  = require("url");

var server = http.createServer(function(req, res) {

    var callback = url.parse(req.url, true).query.callback || "myCallback";
    console.log(url.parse(req.url, true).query.callback);

    var data = {
        'name': "Gianpiero",
        'last': "Fiorelli",
        'age': 37
    };

    data = callback + '(' + JSON.stringify(data) + ');';

    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(data);
});

server.listen(process.env.PORT, process.env.IP);

console.log('Server running at '  + process.env.PORT + ':' + process.env.IP);

CORS

3.client_cors.js

$.ajax({
    url: "http://api_test_server.proudlygeek.c9.io/",
    success: function(data) {
        console.log(data);    
    }
});​

4. server_cors.js

var http = require("http"),
    url  = require("url");

var server = http.createServer(function(req, res) {
    console.log(req.headers);

    var data = {
        'name': "Gianpiero",
        'last': "Fiorelli",
        'age': 37
    };

    res.writeHead(200, {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    });

    res.end(JSON.stringify(data));
});

server.listen(process.env.PORT, process.env.IP);

console.log('Server running at '  + process.env.PORT + ':' + process.env.IP);

4

TL; DR

JSONP是一种古老的技巧,它是为了绕过安全限制而创建的,该安全限制禁止我们从其他服务器(不同来源*)获取JSON数据。

该技巧通过使用一个<script>从该位置请求JSON 的标签来实现,例如:{ "user":"Smith" },但包装在一个函数中,即实际的JSONP(“带有填充的JSON”):

peopleDataJSONP({"user":"Smith"})

以这种形式接收它使我们能够使用peopleDataJSONP函数中的数据。JSONP是一种不好的做法,请不要使用它(请参阅下文)


问题

假设我们正在浏览ourweb.com,我们想从中获取JSON数据(或实际上是任何原始数据)anotherweb.com。如果我们要使用GET请求(如XMLHttpRequestfetch呼叫$.ajax等),我们的浏览器会告诉我们,此丑陋的错误是不允许的:

Chrome CORS控制台错误

如何获取我们想要的数据?好吧,<script>标签不受整个服务器(来源*)的限制!这就是为什么我们可以从任何服务器(例如CDN)加载jQuery或Google Maps之类的库而不会出现任何错误。

要点:如果考虑一下,这些库是实际的,可运行的JS代码(通常是内置所有逻辑的庞大函数)。但是原始数据?JSON数据不是代码。没什么可跑的。这只是纯数据。

因此,无法处理或操纵我们的宝贵数据。浏览器将下载我们<script>标记指向的数据,并且在处理时会抱怨:

wtf是{"user":"Smith"}我们加载的废话吗?这不是代码。我无法计算,语法错误!


JSONP骇客

利用旧数据的旧方法?我们需要该服务器以某种逻辑发送它,因此,在加载它时,浏览器中的代码将能够使用所述数据。因此,外部服务器会在JS函数中向我们发送JSON数据。数据本身被设置为该函数的输入。看起来像这样:

peopleDataJSONP({"user":"Smith"})

这使得我们的浏览器可以解析为JS代码而不会抱怨!与jQuery库完全一样。现在,要获得这种效果,客户端通常会向其“询问” JSONP友好服务器:

<script src="https://anotherweb.com/api/data-from-people.json?myCallback=peopleDataJSONP"></script>

我们的浏览器将收到带有该函数名称的JSONP,因此我们需要在代码中具有相同名称的函数,如下所示:

const peopleDataJSONP = function(data){
  alert(data.user); // "Smith"
}

或像这样,同样的结果:

function peopleDataJSONP(data){
  alert(data.user); // "Smith"
}

浏览器将下载并运行JSONP,它调用我们的function,其中参数data为我们的JSON。现在,我们可以随意处理数据。


不要使用JSONP,请使用CORS

JSONP是一个跨站点黑客,但有一些缺点:

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

要点是,现在不需要使用它了

JSONP是从另一台服务器获取JSON数据的技巧,但是如果我们需要其他类型的跨站点内容,则会违反相同的安全原则(Same-Origin)。

您应该在这里阅读有关CORS的信息,但要点是:

跨域资源共享(CORS)是一种机制,该机制使用附加的HTTP标头来告诉浏览器以在一个来源运行的Web应用程序访问来自另一个来源的选定资源。Web应用程序请求其来源(域,协议或端口)不同的资源时,将执行跨域HTTP请求。



* origin由3种东西定义:protocolporthost。因此,例如,https://web.com起源不同于http://web.com(不同的协议)和https://web.com:8081(不同的端口),并且显然https://thatotherweb.net(不同的主机)


1

JSONP表示带有Padding的JSON

这是该网站,上面有很多示例其中包括从最简单的使用此技术到最先进的平面JavaScript的解释:

w3schools.com / JSONP

上面描述的我最喜欢的技术之一是Dynamic JSON Result,它允许将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)
);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.