O que é JSONP e por que foi criado?

2129

Eu entendo JSON, mas não JSONP. O documento da Wikipedia sobre JSON é (foi) o principal resultado da pesquisa para JSONP. Diz o seguinte:

JSONP ou "JSON com preenchimento" é uma extensão JSON em que um prefixo é especificado como um argumento de entrada da própria chamada.

Hã? Que ligação? Isso não faz nenhum sentido para mim. JSON é um formato de dados. Não há ligação.

O segundo resultado da pesquisa é de um cara chamado Remy , que escreve isso sobre JSONP:

JSONP é a injeção de tag de script, passando a resposta do servidor para uma função especificada pelo usuário.

Eu posso entender isso, mas ainda não faz sentido.


Então, o que é JSONP? Por que foi criado (que problema resolve)? E por que eu iria usá-lo?


Adendo : Acabei de criar uma nova página para JSONP na Wikipedia; agora possui uma descrição clara e completa do JSONP, com base na resposta do jvenema .

Cheeso
fonte
29
Para o registro, NÃO use JSONP se você não confiar no servidor com o qual está falando 100%. Se estiver comprometido, sua página será trivialmente comprometida.
Ninjagecko 27/01
7
Observe também que o JSONP pode ser invadido se não for implementado corretamente.
Pacerier 29/03
3
Gostaria de dar crédito ao autor do JSONP, que deu a filosofia por trás disso: o arquivo de Bob Ippolito no JSONP . Ele apresenta o JSONP como "uma metodologia padrão independente de nova tecnologia para o método de tag de script para busca de dados entre domínios".
harshvchawla

Respostas:

2047

Na verdade, não é muito complicado ...

Digamos que você esteja no domínio example.come deseje fazer uma solicitação ao domínio example.net. Para fazer isso, você precisa cruzar os limites do domínio , um não-não na maioria dos idiomas da navegação.

O único item que ignora essa limitação são as <script>tags. Quando você usa uma tag de script, a limitação do domínio é ignorada, mas em circunstâncias normais, você realmente não pode fazer nada com os resultados, o script é avaliado.

Enter JSONP. Ao fazer sua solicitação para um servidor habilitado para JSONP, você passa um parâmetro especial que informa ao servidor um pouco sobre sua página. Dessa forma, o servidor é capaz de agrupar sua resposta de uma maneira que sua página possa suportar.

Por exemplo, digamos que o servidor espere um parâmetro chamado callbackpara ativar seus recursos JSONP. Em seguida, sua solicitação seria semelhante a:

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

Sem JSONP, isso pode retornar algum objeto JavaScript básico, como:

{ foo: 'bar' }

No entanto, com JSONP, quando o servidor recebe o parâmetro "callback", o resultado é um pouco diferente, retornando algo como isto:

mycallback({ foo: 'bar' });

Como você pode ver, agora ele chamará o método que você especificou. Então, na sua página, você define a função de retorno de chamada:

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

E agora, quando o script for carregado, ele será avaliado e sua função será executada. Voila, solicitações entre domínios!

Também vale a pena notar o principal problema do JSONP: você perde muito controle da solicitação. Por exemplo, não há uma maneira "agradável" de recuperar os códigos de falha adequados. Como resultado, você acaba usando temporizadores para monitorar a solicitação, etc., o que é sempre um pouco suspeito. A proposição para JSONRequest é uma ótima solução para permitir scripts entre domínios, manter a segurança e permitir o controle adequado da solicitação.

Hoje em dia (2015), o CORS é a abordagem recomendada vs. JSONRequest. O JSONP ainda é útil para suporte a navegadores mais antigos, mas dadas as implicações de segurança, a menos que você não tenha escolha, o CORS é a melhor opção.

jvenema
fonte
180
Observe que o uso do JSONP tem algumas implicações de segurança. Como o JSONP é realmente javascript, ele pode fazer tudo o que o javascript pode fazer, portanto, você precisa confiar no provedor dos dados JSONP. Eu escrevi um post no blog sobre isso aqui: erlend.oftedal.no/blog/?blogid=97
Erlend
72
Existe realmente alguma nova implicação de segurança no JSONP que não esteja presente em uma tag <script>? Com uma tag de script, o navegador confia implicitamente no servidor para fornecer Javascript não prejudicial, que o navegador avalia cegamente. JSONP muda esse fato? Parece que não.
Cheeso
23
Não, não faz. Se você confia na entrega do javascript, o mesmo se aplica ao JSONP.
Jvenema
15
Vale a pena notar que você pode aumentar um pouco a segurança alterando a maneira como os dados são retornados. Se você retornar o script no formato JSON verdadeiro, como mycallback ('{"foo": "bar"}') (observe que o parâmetro agora é uma cadeia), poderá analisar os dados manualmente para "limpá-lo" antes avaliação.
Jvenema
8
CURL é uma solução do lado do servidor, não do lado do cliente. Eles servem a dois propósitos diferentes.
jvenema
712

JSONP é realmente um truque simples para superar a mesma política de domínio XMLHttpRequest . (Como você sabe, não é possível enviar uma solicitação AJAX (XMLHttpRequest) para um domínio diferente.)

Portanto, em vez de usar XMLHttpRequest , precisamos usar tags HTML de script , aquelas que você costuma usar para carregar arquivos js, para que js obtenha dados de outro domínio. Soa estranho?

A coisa é - as tags de script podem ser usadas de maneira semelhante ao XMLHttpRequest ! Veja isso:

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

Você terminará com um segmento de script que se parece com este após carregar os dados:

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

No entanto, isso é um pouco inconveniente, porque precisamos buscar esse array na tag de script . Então, os criadores do JSONP decidiram que isso funcionaria melhor (e é):

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

Percebe a função my_callback por lá? Então - quando o servidor JSONP receber sua solicitação e encontrar o parâmetro de retorno de chamada - em vez de retornar a matriz js simples, ele retornará isso:

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

Veja onde está o lucro: agora temos o retorno de chamada automático (my_callback) que será acionado assim que recebermos os dados.
É tudo o que há para saber sobre o JSONP : são tags de retorno de chamada e script.

NOTA: estes são exemplos simples de uso de JSONP, não são scripts prontos para produção.

Exemplo básico de JavaScript (feed simples do Twitter usando JSONP)

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

Exemplo básico de jQuery (feed simples do Twitter usando JSONP)

<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 significa JSON com preenchimento . (técnica muito mal nomeada, pois realmente não tem nada a ver com o que a maioria das pessoas pensaria como "preenchimento".)

Aquele cara
fonte
34
Obrigado pela explicação da tag de script. Não consegui descobrir como a política de segurança entre domínios foi ignorada pelo JSONP. Após a explicação me sinto um litte estúpido para perder o ponto ...
Eduard
13
Esta é uma resposta complementar muito boa à resposta do jvenema - não entendi por que o retorno de chamada era necessário até você apontar que os dados do json teriam que ser acessados ​​por meio do elemento de script.
Matt
5
Obrigado por uma explicação tão lúcida. Eu gostaria que meus livros da faculdade foram escritos por pessoas como você :)
hashbrown
1
Boa explicação ao invés da anterior. Obviamente, seu trecho "costuma ser usado para carregar arquivos js, para que os js obtenham dados de outro domínio. Soa estranho?" também é revelador para mim. Código de exemplo em muito ilustre.
SIslam
o preenchimento não precisa ser literal. é uma espécie de metáfora. portanto, pode significar "JSON com alguns 'espaços'". lol
marvinIsSacul
48

O JSONP funciona construindo um elemento "script" (na marcação HTML ou inserido no DOM via JavaScript), que solicita um local de serviço de dados remoto. A resposta é um javascript carregado no seu navegador com o nome da função predefinida, juntamente com o parâmetro sendo passado, que é o pedido de dados JSON. Quando o script é executado, a função é chamada junto com os dados JSON, permitindo que a página solicitante receba e processe os dados.

Para mais informações, visite: https://blogs.sap.com/2013/07/15/secret-behind-jsonp/

snippet de código do lado do cliente

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

Parte do servidor do código 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 . ")";
?>
Ajain Vivek
fonte
3
o link no topo apenas 404 agora
Kevin Beal
O conteúdo desse link já está disponível em http://scn.sap.com/community/developer-center/front-end/blog/2013/07/15/secret-behind-jsonp .
ᴠɪɴᴄᴇɴᴛ
42

Porque você pode solicitar ao servidor que acrescente um prefixo ao objeto JSON retornado. Por exemplo

function_prefix(json_object);

para que o navegador eval"inline" a sequência JSON como uma expressão. Esse truque possibilita ao servidor "injetar" o código javascript diretamente no navegador do cliente, ignorando as restrições da "mesma origem".

Em outras palavras, você pode obter uma troca de dados entre domínios .


Normalmente, XMLHttpRequestnão permite a troca de dados entre domínios diretamente (é preciso passar por um servidor no mesmo domínio), enquanto:

<script src="some_other_domain/some_data.js&prefix=function_prefix> `pode-se acessar dados de um domínio diferente da origem.


Também vale a pena notar: mesmo que o servidor deva ser considerado "confiável" antes de tentar esse tipo de "truque", os efeitos colaterais de uma possível alteração no formato do objeto etc. podem ser contidos. Se uma function_prefix(ou seja, uma função js adequada) for usada para receber o objeto JSON, a referida função poderá executar verificações antes de aceitar / processar mais os dados retornados.

jldupont
fonte
"acrescentar um prefixo" é confuso :)
jub0bs 22/01
19

O JSONP é uma ótima maneira de contornar erros de script entre domínios. Você pode consumir um serviço JSONP exclusivamente com o JS sem precisar implementar um proxy AJAX no lado do servidor.

Você pode usar o serviço b1t.co para ver como ele funciona. Este é um serviço JSONP gratuito que permite que você reduza seus URLs. Aqui está o URL a ser usado para o serviço:

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

Por exemplo, a chamada, http://b1t.co/Site/api/External/MakeUrlWithGet?callback=whateverJavascriptName&url=google.com

retornaria

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

E assim, quando esse get é carregado em seus js como um src, ele executa automaticamente o javascriptName que você deve implementar como função de retorno de chamada:

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

Para realmente fazer a chamada JSONP, você pode fazer isso de várias maneiras (incluindo o uso de jQuery), mas aqui está um exemplo JS puro:

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);
}

Um exemplo passo a passo e um serviço da web jsonp para praticar estão disponíveis em: este post

dardawk
fonte
2
Obrigado por postar sua resposta! Observe que você deve postar as partes essenciais da resposta aqui, neste site, ou sua postagem corre o risco de ser excluída. Consulte as Perguntas frequentes, onde ele menciona respostas que são 'pouco mais que um link'. Você ainda pode incluir o link, se desejar, mas apenas como uma 'referência'. A resposta deve ser independente, sem a necessidade do link.
Taryn
14

Um exemplo simples para o uso de 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;

    ?>    
sarath joseph
fonte
8

Antes de entender o JSONP, é necessário conhecer o formato JSON e o XML. Atualmente, o formato de dados mais frequentemente usado na web é XML, mas XML é muito complicado. Isso torna os usuários inconvenientes ao processo incorporado nas páginas da Web.

Para que o JavaScript possa trocar dados com facilidade, mesmo como o programa de processamento de dados, usamos o texto de acordo com os objetos JavaScript e desenvolvemos um formato simples de troca de dados, que é JSON. JSON pode ser usado como dados ou como um programa JavaScript.

O JSON pode ser incorporado diretamente no JavaScript, usando-o, você pode executar diretamente certo programa JSON, mas devido a restrições de segurança, o mecanismo Sandbox do navegador desativa a execução de código JSON entre domínios.

Para que o JSON possa ser passado após a execução, desenvolvemos um JSONP. O JSONP ignora os limites de segurança do navegador com a funcionalidade JavaScript Callback e a tag <script>.

Portanto, em resumo, ele explica o que é JSONP, que problema resolve (quando usá-lo).

Marcus Thornton
fonte
4
Fiz uma votação negativa porque não acredito na afirmação de que XML era o formato de dados mais usado na Web em dez '15.
precisa saber é o seguinte
Ainda não se sabe por que o jsonp é usado em vez do json. De onde vêm todas essas restrições de segurança? Por que podemos usar o jsonp, mas não o json, para domínios cruzados?
Merunas Grincalaitis
6

TL; DR

JSONP é um truque antigo inventado para contornar a restrição de segurança que nos proíbe obter dados JSON de um servidor diferente (uma origem diferente * ).

O truque funciona usando uma <script>tag que solicita o JSON daquele local, por exemplo { "user":"Smith" }:, mas envolvido em uma função, o JSONP real ("JSON with Padding"):

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

Recebê-lo desta forma nos permite usar os dados em nossa peopleDataJSONPfunção. JSONP é uma prática ruim , não a use (leia abaixo)


O problema

Digamos que estamos navegando ourweb.come queremos obter dados JSON (ou quaisquer dados brutos) anotherweb.com. Se usarmos a solicitação GET (como XMLHttpRequest, umfetch chamada, $.ajaxetc.), nosso navegador nos dirá que não é permitido com este erro feio:

Erro no console do Chrome CORS

Como obter os dados que queremos? Bem, as <script>tags não estão sujeitas a toda essa restrição do servidor (origem *)! É por isso que podemos carregar uma biblioteca como o jQuery ou o Google Maps de qualquer servidor, como uma CDN, sem erros.

Ponto importante : se você pensar bem, essas bibliotecas são códigos JS reais e executáveis (geralmente uma função massiva com toda a lógica). Mas dados brutos? Dados JSON não são código . Não há nada para correr; são apenas dados simples.

Portanto, não há como manipular ou manipular nossos dados preciosos. O navegador fará o download dos dados apontados pela nossa <script>tag e, ao processar, reclamará com razão:

wtf é essa {"user":"Smith"}porcaria que carregamos? Não é código. Não consigo calcular, erro de sintaxe!


O corte JSONP

A maneira antiga / hacky de utilizar esses dados? Precisamos desse servidor para enviá-lo com alguma lógica, portanto, quando estiver carregado, seu código no navegador poderá usar esses dados. Portanto, o servidor estrangeiro nos envia os dados JSON dentro de uma função JS. Os dados em si são configurados como entrada dessa função. Se parece com isso:

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

o que torna o código JS que nosso navegador analisará sem reclamar! Exatamente como acontece com a biblioteca jQuery. Agora, para obtê-lo assim, o cliente "solicita" o servidor compatível com JSONP, geralmente feito assim:

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

Nosso navegador receberá o JSONP com esse nome de função, portanto, precisamos de uma função com o mesmo nome em nosso código, assim:

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

Ou assim, o mesmo resultado:

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

O navegador fará o download do JSONP e o executará, que chama nossa função , onde o argumento dataserá nosso JSON. Agora podemos fazer com nossos dados o que quisermos.


Não use JSONP, use CORS

JSONP é um hack entre sites com algumas desvantagens:

  • Só podemos executar solicitações GET
  • Como é uma solicitação GET acionada por uma simples tag de script, não obtemos erros úteis ou informações de progresso
  • Existem também algumas preocupações de segurança, como a execução no código JS do cliente que pode ser alterado para uma carga maliciosa
  • Ele resolve o problema apenas com dados JSON, mas a política de segurança Same-Origin se aplica a outros dados (WebFonts, imagens / vídeo desenhados com drawImage () ...)
  • Não é muito elegante nem legível.

O argumento é que não há necessidade de usá-lo hoje em dia .

JSONP é o truque para obter dados JSON de outro servidor, mas violaremos o mesmo princípio de segurança (mesma origem) se precisarmos de outros tipos de coisas entre sites.

Você deve ler sobre o CORS aqui , mas a essência é:

O Compartilhamento de Recursos de Origem Cruzada (CORS) é um mecanismo que usa cabeçalhos HTTP adicionais para instruir os navegadores a fornecerem um aplicativo Web em execução em uma origem, acesso a recursos selecionados de uma origem diferente. Um aplicativo Web executa uma solicitação HTTP de origem cruzada quando solicita um recurso que tenha uma origem (domínio, protocolo ou porta) diferente da sua.



* origem é definida por três coisas: protocolo , porta e host . Então, por exemplo, https://web.comé uma origem diferente de http://web.com(protocolo diferente) e https://web.com:8081(porta diferente) e obviamente https://thatotherweb.net(host diferente)

Carles Alcolea
fonte
1
Ei, cara, isso forneceu 100% de clareza como nota de rodapé da resposta aprovada! Obrigado por isso ....
M'Baku
4

As ótimas respostas já foram dadas, só preciso fornecer minha peça na forma de blocos de código em javascript (também incluirei uma solução mais moderna e melhor para solicitações de origem cruzada: CORS with HTTP Headers):

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);
Humoyun Ahmad
fonte
1

JSONP significa JSON com preenchimento .

Aqui está o site, com ótimos exemplos , com a explicação do uso mais simples dessa técnica ao mais avançado no JavaScript do plano:

w3schools.com / JSONP

Uma das minhas técnicas mais favoritas descritas acima é o resultado JSON dinâmico , que permite enviar JSON para o arquivo PHP no parâmetro URL e permitir que o arquivo PHP também retorne um objeto JSON com base nas informações obtidas .

Ferramentas como o jQuery também possuem facilidades para usar o JSONP :

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