JavaScript:我如何创建JSONP?

40
我有两个域名,example1.com 和 example2.com。
我希望从 example1.com 访问我在 example2.com 上拥有的 JSON API。鉴于这是不被允许的,我想到 - 这正是为什么 JSONP 被创建的原因。
问题是,如何修改我的 JSON API 使其具有 JSONP 功能?
基本上,我该如何创建回调 API?
更新:
我的服务器端语言是 PHP。
8个回答

77

很简单。只需在GET请求中接受一个名为callback的参数。

然后用你的数据包裹回调JavaScript函数。

PHP示例:

<?php

$data = '{}'; // json string

if(array_key_exists('callback', $_GET)){

    header('Content-Type: text/javascript; charset=utf8');
    header('Access-Control-Allow-Origin: http://www.example.com/');
    header('Access-Control-Max-Age: 3628800');
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');

    $callback = $_GET['callback'];
    echo $callback.'('.$data.');';

}else{
    // normal JSON string
    header('Content-Type: application/json; charset=utf8');

    echo $data;
}

它的想法很简单,返回一个调用回调函数的JavaScript文件,并将JSON对象作为JavaScript回调函数的第一个参数传递。

您可以使用内置的json_encode()函数从PHP中的数组和对象创建JSON字符串(如上面的示例中包含的$data)。

要使用JSONP服务,您可以使用<script>标记:

<script>
    function receiver(data){
        console.log(data);
    }
</script>
<script src="data-service.php?callback=receiver"></script>

4
哇,难道真的这么简单吗? - Teddi
我需要添加吗:echo 'Access-Control-Allow-Origin: *' echo 'Access-Control-Allow-Methods: GET'? - Teddi
@Mauris,你介意更新你的代码以包含新的XMLHttpRequest v2规范头吗?谢谢。 - Teddi
3
请确保过滤回调参数,只允许字母、数字、点和方括号这些有效字符。否则你将会陷入困境。 - Kent Brewster
@KentBrewster 怎么做呢? - SnareChops
显示剩余3条评论

26

你需要一种服务器端语言,回调参数仅是一个GET参数,你可以读取该参数,将JSON响应包装到函数调用中,然后像这样打印出来:callback(jsonResponse);

我给你留下了一个真正极简的Python示例,因为你没有提到任何服务器端语言:

import os
import cgi

form = cgi.FieldStorage()
callback = form.getvalue('callback','')

address = cgi.escape(os.environ["REMOTE_ADDR"])

json = '{"ip": "'+address+'", "address":"'+address+'"}'

#Allow cross domain XHR
print 'Access-Control-Allow-Origin: *'
print 'Access-Control-Allow-Methods: GET'

if callback != '':
  print 'Content-Type: application/javascript'
  result = callback+'('+json+');'
else:
  print 'Content-Type: application/json'
  result = json

print ''
print result

这是一个小型的JSONP服务的代码,用于由Zach编写的客户端IP地址检索,并且它托管在Google App Engine上。


6

Mauris已经给出了一个可行的示例。我只想补充一点,您应该检查是否存在callback参数,并且如果没有,则返回json数据而不带括号。因此,基本上您的api将是JSON,当给定callback时提供JSON-P。

要使用JSON-P web服务,除非您使用类似YUI或jQuery的框架,否则可以动态创建脚本节点并将其src属性设置为指向web服务。记得在重复使用之前从dom中删除节点,因为这个动态脚本节点只能使用一次。


4
我知道我来晚了,但其中一个回答提到了代码的安全性问题。这里有一篇关于此问题的好文章:http://www.geekality.net/2010/06/27/php-how-to-easily-provide-json-and-jsonp/。以下是应该运行的代码:
<?php header('content-type: application/json; charset=utf-8');

function is_valid_callback($subject)
{
    $identifier_syntax
      = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u';

    $reserved_words = array('break', 'do', 'instanceof', 'typeof', 'case',
      'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 
      'for', 'switch', 'while', 'debugger', 'function', 'this', 'with', 
      'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 
      'extends', 'super', 'const', 'export', 'import', 'implements', 'let', 
      'private', 'public', 'yield', 'interface', 'package', 'protected', 
      'static', 'null', 'true', 'false');

    return preg_match($identifier_syntax, $subject)
        && ! in_array(mb_strtolower($subject, 'UTF-8'), $reserved_words);
}

$data = array(1, 2, 3, 4, 5, 6, 7, 8, 9);
$json = json_encode($data);

# JSON if no callback
if( ! isset($_GET['callback']))
    exit($json);

# JSONP if valid callback
if(is_valid_callback($_GET['callback']))
    exit("{$_GET['callback']}($json)");

# Otherwise, bad request
header('status: 400 Bad Request', true, 400);

如果有人正在使用Angular,这段代码可能无法正常工作。Angular支持JSONP,但会将回调函数替换为类似于angular.callback._0的内容。允许这样的回调函数是否安全? - Bob Vork
@BobVork 我不这样认为,你可以更改$identifier_syntax以包含句点。实际上,我相信我在我的实现中出于同样的原因而这样做了(但在完成实现之前发布了这篇文章)。 - Joel Kinzel

3
// Adds script tag to head of the page
function addScriptToHead(source, code, type) {
    var script = document.createElement('script');
    if (type === 'js') {
        script.setAttribute('type', 'text/javascript');
    }
    if (source !== '') {
        script.setAttribute('src', source);
    }
    if (code !== '') {
        if (document.all && !window.opera)  {
            script.text = code;
        } else {
            script.innerHTML = code;
        }
    }
    document.getElementsByTagName('head')[0].appendChild(script);
}


// Callback function
function addScriptToHead(any_param) {

// do whatever needs to be done

}

//call example

addScriptToHead('http://url_to_receiver_script/index.php&param=anything', '', 'js');

/// 回调脚本应该返回回调函数的名称,即如果您在浏览器中输入

http://url_to_receiver_script/index.php&param=anything

它应该只返回一段文本(现有处理函数的名称):addScriptToHead(any_param)

在任何浏览器中都可以像钟表一样运行。


2

使用jQuery轻松实现客户端功能:

  $.ajax({
        dataType: 'jsonp',
        data: "somedata="+somevalue,
        //this is very important since it's the callback we will and that allow cross domain
        jsonp: 'jsonp_callback',
        url: 'http://example2.com',
        //function we trigger on success
        success: ParseJson
         //error handling not working with jsonP
         //error: handleError
        });

function ParseJson(data)
{
for (var key in data) {
  if (data.hasOwnProperty(key)) {
    alert(key + " -> " + data[key]);
  }
}
}

请确保从服务器端获取适当的json;
不要忘记返回jsonp_callback参数,否则它将无法工作!!!
就是这样。


0

你可以使用Simple JSON for PHP来轻松完成它!它能简化所有的事情!

<?php

  include('../includes/json.php');

  $json = new json('callback', 'myCallback');

  $object = new stdClass();
  $object->FirstName = 'John';
  $object->LastName = 'Doe';
  $array = array(1,'2', 'Pieter', true);
  $jsonOnly = '{"Hello" : "darling"}';
  // Add objects to send
  $json->add('status', '200');
  $json->add("worked");
  $json->add("things", false);
  $json->add('friend', $object);
  $json->add("arrays", $array);
  $json->add("json", $jsonOnly, false);

  /*
  Expected result : 
  myCallback({
    "status": "200",
    "worked": true,
    "things": false,
    "friend": {
        "FirstName": "John",
        "LastName": "Doe"
    },
    "arrays": [
        1,
        "2",
        "Pieter",
        true
    ],
    "json": {
        "Hello": "darling"
    }
  });

  */
  $json->send();
?>

-1

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