JavaScript:如何创建 JSONP?

IT技术 php javascript json cross-domain jsonp
2021-01-27 13:30:20

我有两个域,example1.com 和 example2.com

从example1.com,我想调用我在example2.com 上的JSON API。知道这是不允许的,我想到了 - 这正是创建JSON P 的原因

问题是,如何修改我的 JSON API 以使其支持 JSONP?

基本上,我如何创建回调 api?

更新

我的服务器端语言是 PHP

6个回答

很简单。只需接受callback在 GET 中调用的参数

然后将回调 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()函数$data从 PHP 中的数组和对象创建 JSON 字符串(在我们上面的示例中包含)。

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

<script>
    function receiver(data){
        console.log(data);
    }
</script>
<script src="data-service.php?callback=receiver"></script>
请务必过滤您的回调参数,以便只有字母、数字、点和方括号是有效字符。否则你就进入了一个受伤的世界。
2021-03-18 13:30:20
@Teddi:这些标头是为了支持即将推出的 XMLHttpRequest 2 规范(w3.org/TR/access-control),顺便说一句,它们已经在 Firefox 3.5 上工作(developer.mozilla.org/en/HTTP_access_control
2021-03-19 13:30:20
哇,真的就这么简单吗?
2021-03-26 13:30:20
@Mauris,您介意更新您的代码以包含新的 XMLHttpRequest v2 规范标头吗?谢谢
2021-03-29 13:30:20
我是否需要添加: echo 'Access-Control-Allow-Origin: *' echo 'Access-Control-Allow-Methods: GET' ???
2021-03-31 13:30:20

您需要一种服务器端语言,回调参数只是一个 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

这是用于检索Zach制作的客户端 IP 地址的小型JSONP 服务的代码,它托管在Google App Engine 上

Mauris 已经给了你一个可行的例子。我只想补充一点,您应该检查callback参数是否存在且非空,如果不存在,则按原样返回 json 数据,不带括号。所以基本上你的 api 将是 JSON,并在callback给出时提供 JSON-P

要使用 JSON-P 网络服务,除非您使用像 YUI 或 jQuery 这样的框架,否则您可以简单地动态创建脚本节点并将其src属性设置为指向网络服务。请记住在再次重复之前从 dom 中删除该节点,因为此动态脚本节点仅供一次性使用。

我知道我参加聚会迟到了,并且在其中一个答案中有关于代码安全性的评论。这是一篇关于这个的好文章:

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. 允许这样的回调会不安全吗?
2021-03-20 13:30:20
@BobVork 我不这么认为,您也可以将其更改$identifier_syntax为包含句点。事实上,我相信出于这个原因,我最终在我的实现中这样做了(但在我完成我的实现之前发布了这个)。
2021-04-06 13:30:20
// 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¶m=anything

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

在任何浏览器中都像时钟一样工作。