Che cos'è JSONP e perché è stato creato?


2129

Capisco JSON, ma non JSONP. Il documento di Wikipedia su JSON è (era) il miglior risultato di ricerca per JSONP. Dice questo:

JSONP o "JSON con padding" è un'estensione JSON in cui un prefisso viene specificato come argomento di input della chiamata stessa.

Eh? Quale chiamata? Non ha alcun senso per me. JSON è un formato di dati. Non c'è chiamata.

Il secondo risultato di ricerca proviene da un ragazzo di nome Remy , che scrive questo su JSONP:

JSONP è l'iniezione di tag script, passando la risposta dal server a una funzione specificata dall'utente.

Posso capirlo, ma non ha ancora senso.


Quindi cos'è JSONP? Perché è stato creato (quale problema risolve)? E perché dovrei usarlo?


Addendum : ho appena creato una nuova pagina per JSONP su Wikipedia; ora ha una descrizione chiara e completa di JSONP, basata sulla risposta di Jvenema .


29
Per la cronaca, NON utilizzare JSONP se non ti fidi del server con cui stai parlando al 100%. Se è compromessa, la tua pagina web sarà banalmente compromessa.
ninjagecko,

7
Si noti inoltre che JSONP può essere dirottato se non implementato correttamente.
Pacerier,

3
Vorrei dare credito all'autore di JSONP che ha dato la sua filosofia: l'archivio di Bob Ippolito su JSONP . Introduce JSONP come "una nuova metodologia agnostica standard tecnologica per il metodo dei tag di script per il recupero di dati tra domini".
harshvchawla,

Risposte:


2047

In realtà non è troppo complicato ...

Supponi di essere sul dominio example.come desideri fare una richiesta al dominio example.net. Per fare ciò, è necessario oltrepassare i confini del dominio , un no-no nella maggior parte dei browserland.

L'unico elemento che elude questa limitazione sono i <script>tag. Quando si utilizza un tag di script, la limitazione del dominio viene ignorata, ma in circostanze normali, non si può davvero fare nulla con i risultati, lo script viene semplicemente valutato.

Enter JSONP. Quando si effettua la richiesta a un server che è abilitato JSONP, si passa un parametro speciale che dice al server un po 'della tua pagina. In questo modo, il server è in grado di racchiudere la sua risposta in un modo che la tua pagina può gestire.

Ad esempio, supponiamo che il server si aspetti un parametro chiamato callbackper abilitare le sue funzionalità JSONP. Quindi la tua richiesta sarebbe simile a:

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

Senza JSONP, questo potrebbe restituire alcuni oggetti JavaScript di base, in questo modo:

{ foo: 'bar' }

Tuttavia, con JSONP, quando il server riceve il parametro "callback", avvolge il risultato in modo leggermente diverso, restituendo qualcosa del genere:

mycallback({ foo: 'bar' });

Come puoi vedere, ora invocherà il metodo specificato. Quindi, nella tua pagina, definisci la funzione di callback:

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

E ora, quando lo script viene caricato, verrà valutato e la tua funzione verrà eseguita. Voila, richieste tra domini!

Vale anche la pena notare un grosso problema con JSONP: perdi molto controllo sulla richiesta. Ad esempio, non esiste un modo "carino" per recuperare i codici di errore corretti. Di conseguenza, si finisce per utilizzare i timer per monitorare la richiesta, ecc., Che è sempre un po 'sospetto. La proposta per JSONRequest è un'ottima soluzione per consentire lo scripting tra domini, mantenere la sicurezza e consentire il controllo adeguato della richiesta.

In questi giorni (2015), CORS è l'approccio raccomandato rispetto a JSONRequest. JSONP è ancora utile per il supporto del browser precedente, ma date le implicazioni di sicurezza, a meno che tu non abbia scelta CORS è la scelta migliore.


180
Si noti che l'utilizzo di JSONP ha alcune implicazioni per la sicurezza. Dato che JSONP è davvero javascript, può fare tutto ciò che javascript può fare, quindi devi fidarti del provider dei dati JSONP. Ho scritto un post sul blog qui: erlend.oftedal.no/blog/?blogid=97
Erlend

72
C'è davvero qualche nuova implicazione sulla sicurezza in JSONP che non è presente in un tag <script>? Con un tag script il browser si fida implicitamente del server per fornire Javascript non dannoso, che il browser valuta ciecamente. JSONP cambia questo fatto? Sembra di no.
Cheeso,

23
No, non lo è. Ti fidi di consegnare il javascript, la stessa cosa vale per JSONP.
jvenema,

15
Vale la pena notare che è possibile aumentare leggermente la sicurezza modificando la modalità di restituzione dei dati. Se si restituisce lo script nel vero formato JSON come mycallback ('{"foo": "bar"}') (si noti che il parametro è ora una stringa), è possibile analizzare manualmente i dati per "pulirlo" prima valutare.
jvenema,

8
CURL è una soluzione lato server, non lato client. Servono a due scopi diversi.
jvenema,

712

JSONP è davvero un semplice trucco per superare la stessa politica di dominio XMLHttpRequest . (Come sapete, non è possibile inviare una richiesta AJAX (XMLHttpRequest) a un dominio diverso.)

Quindi - invece di usare XMLHttpRequest dobbiamo usare i tag HTML dello script , quelli che di solito usi per caricare i file js, affinché js ottenga i dati da un altro dominio. Suona strano?

Il fatto è che risulta che i tag di script possono essere utilizzati in modo simile a XMLHttpRequest ! Controllalo:

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

Si finirà con un segmento di script che assomiglia a questo dopo aver caricato i dati:

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

Tuttavia, questo è un po 'scomodo, perché dobbiamo recuperare questo array dal tag script . Quindi i creatori di JSONP hanno deciso che funzionerà meglio (e lo è):

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

Notate la funzione my_callback laggiù? Quindi - quando il server JSONP riceve la tua richiesta e trova il parametro callback - invece di restituire un array js normale, restituirà questo:

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

Guarda dove è il profitto: ora otteniamo il callback automatico (my_callback) che verrà attivato una volta che avremo i dati.
Questo è tutto ciò che c'è da sapere su JSONP : è un callback e tag di script.

NOTA: questi sono semplici esempi di utilizzo di JSONP, non si tratta di script pronti per la produzione.

Esempio JavaScript di base (semplice feed Twitter che utilizza 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>

Esempio jQuery di base (semplice feed Twitter che utilizza 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 sta per JSON con imbottitura . (tecnica molto mal denominata in quanto non ha nulla a che fare con ciò che la maggior parte della gente considererebbe "imbottitura".)


34
Grazie per la spiegazione del tag dello script. Non sono riuscito a capire come la politica di sicurezza tra domini sia stata ignorata da JSONP. Dopo la spiegazione mi sento un po 'stupido a perdere il punto ...
Eduard

13
Questa è un'ottima risposta complementare alla risposta di Jvenema: non ho capito perché il callback fosse necessario fino a quando non hai sottolineato che i dati JSON avrebbero dovuto essere accessibili tramite l'elemento script.
Matt

5
Grazie per una spiegazione così lucida. Vorrei che i miei libri di testo del college fossero scritti da persone come te :)
hashbrown,

1
Buona spiegazione piuttosto che la precedente. Ovviamente, il tuo estratto "quelli che usi abitualmente per caricare i file js, affinché js ottenga i dati da un altro dominio. Sembra strano?" è anche apri gli occhi per me. Codice di esempio molto illustre.
SIslam,

l'imbottitura non deve essere letterale. è una specie di metafora. quindi può significare "JSON con alcuni 'spazi'". lol
marvinIsSacul

48

JSONP funziona costruendo un elemento "script" (nel markup HTML o inserito nel DOM tramite JavaScript), che richiede una posizione del servizio dati remoto. La risposta è un javascript caricato sul tuo browser con il nome della funzione predefinita insieme al parametro passato che è richiesto dai dati JSON. Quando viene eseguito lo script, la funzione viene chiamata insieme ai dati JSON, consentendo alla pagina richiedente di ricevere ed elaborare i dati.

Per ulteriori letture visita: https://blogs.sap.com/2013/07/15/secret-behind-jsonp/

frammento di codice lato client

    <!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 server di codice 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
il link in cima ora è solo 404s
Kevin Beal


42

Perché è possibile chiedere al server di anteporre un prefisso all'oggetto JSON restituito. Per esempio

function_prefix(json_object);

affinché il browser eval"inline" la stringa JSON come espressione. Questo trucco consente al server di "iniettare" il codice javascript direttamente nel browser del client e questo con l'esclusione delle restrizioni "stessa origine".

In altre parole, è possibile ottenere lo scambio di dati tra domini .


Normalmente, XMLHttpRequestnon consente lo scambio di dati tra domini direttamente (è necessario passare attraverso un server nello stesso dominio) mentre:

<script src="some_other_domain/some_data.js&prefix=function_prefix> `si può accedere ai dati da un dominio diverso dall'origine.


Vale anche la pena notare: anche se il server dovrebbe essere considerato "attendibile" prima di tentare quel tipo di "trucco", gli effetti collaterali di possibili cambiamenti nel formato degli oggetti ecc. Possono essere contenuti. Se function_prefixper ricevere l'oggetto JSON viene utilizzata una (ovvero una funzione js corretta), tale funzione può eseguire controlli prima di accettare / elaborare ulteriormente i dati restituiti.


"aggiungi un prefisso" è confuso :)
jub0bs

19

JSONP è un ottimo strumento per aggirare gli errori di scripting tra domini. È possibile utilizzare un servizio JSONP esclusivamente con JS senza dover implementare un proxy AJAX sul lato server.

È possibile utilizzare il servizio b1t.co per vedere come funziona. Questo è un servizio JSONP gratuito che ti consente di minimizzare i tuoi URL. Ecco l'URL da utilizzare per il servizio:

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

Ad esempio la chiamata, http://b1t.co/Site/api/External/MakeUrlWithGet?callback=whateverJavascriptName&url=google.com

sarebbe tornato

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

E quindi quando questo viene caricato nel tuo js come src, verrà eseguito automaticamente qualunque sia JavaScript JavaScript che dovresti implementare come funzione di callback:

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

Per effettuare effettivamente la chiamata JSONP, è possibile farlo in diversi modi (incluso l'utilizzo di jQuery) ma ecco un esempio 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);
}

Un esempio passo passo e un servizio web jsonp su cui esercitarsi sono disponibili su: questo post


2
Grazie per aver pubblicato la tua risposta! Si prega di notare che è necessario pubblicare le parti essenziali della risposta qui, su questo sito, o che i tuoi messaggi rischiano di essere eliminati Vedi le FAQ in cui menziona risposte che sono "a malapena più di un link". Se lo desideri, puoi comunque includere il link, ma solo come "riferimento". La risposta dovrebbe essere autonoma senza il collegamento.
Taryn

14

Un semplice esempio per l'utilizzo di 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

Prima di comprendere JSONP, è necessario conoscere il formato JSON e XML. Attualmente il formato di dati più utilizzato sul Web è XML, ma XML è molto complicato. Ciò rende gli utenti scomodi nell'elaborazione integrata nelle pagine Web.

Per rendere JavaScript in grado di scambiare facilmente i dati, anche come programma di elaborazione dei dati, utilizziamo la formulazione in base agli oggetti JavaScript e abbiamo sviluppato un formato di scambio di dati semplice, che è JSON. JSON può essere utilizzato come dati o come programma JavaScript.

JSON può essere direttamente incorporato in JavaScript, utilizzandoli è possibile eseguire direttamente determinati programmi JSON, ma a causa di vincoli di sicurezza, il meccanismo Sandbox del browser disabilita l'esecuzione del codice JSON tra domini.

Per fare in modo che JSON possa essere passato dopo l'esecuzione, abbiamo sviluppato un JSONP. JSONP ignora i limiti di sicurezza del browser con la funzionalità di richiamata JavaScript e il tag <script>.

Quindi, in breve, spiega cos'è JSONP, quale problema risolve (quando usarlo).


4
Ho declassato questo perché non credo che l'XML fosse il formato di dati più utilizzato sul web nel dicembre '15.
RobbyD,

Non si chiede ancora perché jsonp sia usato al posto di json. Da dove provengono tutte quelle restrizioni di sicurezza? Perché possiamo usare jsonp ma non json per domini diversi?
Meruna Grincalaitis,

6

TL; DR

JSONP è un vecchio trucco inventato per aggirare la limitazione di sicurezza che ci proibisce di ottenere dati JSON da un server diverso (un'origine diversa * ).

Il trucco funziona usando un <script>tag che richiede il JSON da quel luogo, ad es .: { "user":"Smith" }, ma racchiuso in una funzione, il JSONP attuale ("JSON con Padding"):

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

La ricezione in questo modulo ci consente di utilizzare i dati all'interno della nostra peopleDataJSONPfunzione. JSONP è una cattiva pratica , non usarlo (leggi sotto)


Il problema

Supponiamo che stiamo navigando ourweb.come che vogliamo ottenere dati JSON (o qualsiasi dato non elaborato) da anotherweb.com. Se dovessimo usare la richiesta GET (come XMLHttpRequest, una fetchchiamata, $.ajaxecc.), Il nostro browser ci direbbe che non è permesso con questo brutto errore:

Errore console Chrome CORS

Come ottenere i dati desiderati? Bene, i <script>tag non sono soggetti a questa limitazione dell'intero server (origine *)! Ecco perché possiamo caricare una libreria come jQuery o Google Maps da qualsiasi server, come un CDN, senza errori.

Punto importante : se ci pensate, quelle librerie sono reali, codice JS eseguibile (di solito una funzione enorme con tutta la logica all'interno). Ma dati grezzi? I dati JSON non sono codici . Non c'è niente da correre; sono solo dati semplici.

Pertanto, non c'è modo di gestire o manipolare i nostri preziosi dati. Il browser scaricherà i dati indicati dal nostro <script>tag e durante l'elaborazione si lamenterà giustamente:

wtf è questa {"user":"Smith"}merda che abbiamo caricato? Non è un codice. Non riesco a calcolare, errore di sintassi!


L'hack JSONP

Il vecchio / confuso modo di utilizzare quei dati? Abbiamo bisogno che quel server lo invii con un po 'di logica, quindi quando viene caricato, il tuo codice nel browser sarà in grado di utilizzare tali dati. Quindi il server esterno ci invia i dati JSON all'interno di una funzione JS. I dati stessi sono impostati come input di quella funzione. Sembra così:

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

che lo rende codice JS il nostro browser analizzerà senza lamentarsi! Esattamente come fa con la libreria jQuery. Ora, per ottenerlo in questo modo, il client "chiede" al server compatibile con JSONP, di solito fatto in questo modo:

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

Il nostro browser riceverà JSONP con quel nome di funzione, quindi abbiamo bisogno di una funzione con lo stesso nome nel nostro codice, in questo modo:

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

O come questo, stesso risultato:

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

Il browser scaricherà il JSONP ed eseguirà, che chiama la nostra funzione , dove l'argomento datasarà il nostro JSONP. Ora possiamo fare con i nostri dati qualunque cosa vogliamo.


Non utilizzare JSONP, utilizzare CORS

JSONP è un hack cross-site con alcuni aspetti negativi:

  • Siamo in grado di eseguire solo richieste GET
  • Poiché si tratta di una richiesta GET attivata da un semplice tag di script, non otteniamo errori utili o informazioni sullo stato di avanzamento
  • Ci sono anche alcuni problemi di sicurezza, come l'esecuzione nel codice JS del client che potrebbe essere modificato in un payload dannoso
  • Risolve solo il problema con i dati JSON, ma la politica di sicurezza Same-Origin si applica ad altri dati (Font Web, immagini / video disegnati con drawImage () ...)
  • Non è molto elegante né leggibile.

L'asporto è che non è necessario usarlo al giorno d'oggi .

JSONP è il trucco per ottenere dati JSON da un altro server, ma violeremo lo stesso principio di sicurezza (Same-Origin) se avremo bisogno di altri tipi di cose tra siti.

Dovresti leggere qui su CORS , ma l'essenza è:

Cross-Origin Resource Sharing (CORS) è un meccanismo che utilizza intestazioni HTTP aggiuntive per indicare ai browser di fornire a un'applicazione Web in esecuzione su un'origine, l'accesso a risorse selezionate da un'origine diversa. Un'applicazione Web esegue una richiesta HTTP tra origini quando richiede una risorsa che ha un'origine diversa (dominio, protocollo o porta) dalla propria.



* l'origine è definita da 3 cose: protocollo , porta e host . Quindi, ad esempio, ha https://web.comun'origine diversa rispetto a http://web.com(protocollo diverso) e https://web.com:8081(porta diversa) e ovviamente https://thatotherweb.net(host diverso)


1
Ehi amico, questo ha fornito chiarezza al 100% come nota a piè di pagina della risposta approvata! Grazie per questo ....
M'Baku,

4

Le grandi risposte sono già state fornite, ho solo bisogno di dare il mio pezzo sotto forma di blocchi di codice in JavaScript (includerò anche una soluzione più moderna e migliore per le richieste di origine incrociata: CORS con intestazioni HTTP):

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

1

JSONP sta per JSON con imbottitura .

Ecco il sito, con grandi esempi , con la spiegazione dal più semplice utilizzo di questa tecnica al più avanzato piano JavaScript:

w3schools.com / JSONP

Una delle mie tecniche più preferite sopra descritte è Dynamic JSON Result , che consente di inviare JSON al file PHP nel parametro URL e che consente al file PHP di restituire anche un oggetto JSON in base alle informazioni che ottiene .

Strumenti come jQuery hanno anche funzioni per usare JSONP :

jQuery.ajax({
  url: "https://data.acgov.org/resource/k9se-aps6.json?city=Berkeley",
  jsonp: "callbackName",
  dataType: "jsonp"
}).done(
  response => console.log(response)
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.