-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



domenica 9 gennaio 2011

jsonp nothing to do with pjson

A chi non è capitato sviluppando con javascript di doversi imbattere nel famoso limite imposto dalla same domain policy.
In sintesi questa policy permette di eseguire scripts su pagine che hanno origine dallo stesso sito ed accedere ai metodi/proprietà di queste senza particolari restrizioni, ma impedisce di accedere alla maggior parte dei metodi/proprietà di pagine che hanno origine da siti diversi.
Questa policy è presente sin dai tempi di Netscape 2.0 ed è stata introdotta per motivi di security.
In questi link potete farvi un'idea dei possibili attacchi che sono legati a questa restrizione.
Cross Site Scripting (XSS)
SQL Injection
Cross-Site Request Forgery (CSRF)

Ritornando a noi, il problema è che oramai le nostre mashup application richiedono sempre più dati da terze parti (Flickr, Yahoo, Google ecc.) e la same domain policy ostacolerebbe non poco lo sviluppo della nostra applicazione.

Per bypassare la same domain policy è necessario crearsi una pagina di proxy sul server e delegarlo nella richiesta ad un altro sito così noi rispettiamo la same domain policy perchè chiamiamo il nostro server oppure possiamo utilizzare l'IFrame.

Ma c'è un'alternativa ovverosia utilizzare JSONP.
L'idea è quella di sfruttare il tag script html e utilizzare l'attributo src, che non è soggetto alla restrizione della same domain policy.

Vediamo un esempio:
Richiamiamo il metodo getData e passiamo come parametro una funzione definita nella nostra pagina (in questo caso il nome della funzione è theResponse).

 <script type="text/javascript">
  
   src="http://www.example.com/getData?id=2&callback=theResponse">
  
 </script>  

Se la risposta di questa chiamata riempie il payload JSON con il nome della funzione theResponse passata, il metodo theResponse verrà eseguito:

payload JSON: {Name: "Marco", Surname: "Rossi"}
risposta del servizio: theResponse({Name: "Marco", Surname: "Rossi"})

Funzione definita sul client:
function theResponse(data){
   alert("Name: " + data.Name + ", Surname: " + data.Surname);
}

Comunque, ci sono alcuni limiti tra i quali quello di supportare solo richieste GET e quello di non avere un error handler: se fallisce qualcosa esso lo farà silenziosamente. Normalmente si tende a rispondere sempre con successo e ad inserire nel payload l'errore così da poter essere opportunamento trattato.
Inoltre abbiamo il problema, come abbiamo anticipato all'inizio, della sicurezza e quindi andrebbe utilizzato con servizi trusted o sotto il proprio controllo.

Lato client Dojo ci facilita un po' le cose mettendoci a disposizione dojo.io.script.get

 dojo.require("dojo.io.script");
  
 dojo.io.script.get({
  
 callbackParamName : "callback",
  
 url: "http://example.com/getData?id=2",
  
 load : function(response, ioArgs) {
  
 console.log(response);
  
 return response;
  
 },
  
 error : function(response, ioArgs) {
  
 console.log(response);
  
 return response;
  
 }
  
 });  

Dietro le quinte Dojo gestisce la callback creando una funzione temporanea e mette la risposta nella funzione load. In sintesi, Dojo rimuove il padding al tuo posto e indirizza il risultato nella funzione di load. Attenzione quindi che a callbackParamName occorre indicare il nome del parametro della callback che in questo esempio si chiama 'callback' e non il nome della funzione.

Dal server potremmo anche farci restituire in alternativa anche un javascript puro. Ad esempio una stringa del tipo:

return "var p = {Name: "Marco", Surname: "Rossi"}"

In questo caso lato client utilizziamo il checkString:

 dojo.require("dojo.io.script");
  
 dojo.io.script.get({
  
 checkString : "p",
  
 timeout : 2000,
  
 url : "http://www.example.com/GetData?Id=2",
  
 load : function(response, ioArgs) {
  
 console.log(p);
  
 console.log(response)
  
 },
  
 error : function(response, ioArgs) {
  
 console.log("error", response, ioArgs);
  
 return response;
  
 }
  
 });  

Lato server per creare un servizio WCF REST JSONP in NET 4 potete impostare crossDomainScriptAccessEnabled a true. Qui i dettagli.


Con le esri API js puoi utilizzare esri.request, un wrapper intorno a dojo.io.script.get e dojo.xhrPost che ti consente anche di indicare se usare il proxy per richieste 'corpose'.

Vediamo un esempio:

 dojo.byId("url").value = "http://api.flickr.com/services/feeds/photos_public.gne?tags=earthquakes,us&tagmode=all&format=json";
  
 var requestHandle = esri.request({
  
  url: url.path,
  
  content: url.query,
  
  callbackParamName: "jsoncallback",
  
  load: requestSucceeded,
  
  error: requestFailed
  
 });  

In questo caso il contenuto è scaricato in JSONP. Nell'esempio è stata fatta una chiamata Flickr e Flickr vuole che il nome del parametro di callback utilizzato per passare la funzione si chiami 'jsoncallback'

Nel caso utilizzassimo ESRI REST API il nome del parametro si chiamerebbe 'callback'.

Vediamo un esempio (vedi online):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <meta http-equiv="X-UA-Compatible" content="IE=7" />
  <!--The viewport meta tag is used to improve the presentation and behavior of the samples 
    on iOS devices-->
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
  <title>JSONP Content</title>
  <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/2.1/js/dojo/dijit/themes/claro/claro.css" />
  <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/2.1/js/dojo/dojox/widget/Toaster/Toaster.css" />
  <script type="text/javascript">
    var djConfig = { isDebug: true, parseOnLoad: true };
  </script>
  <script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.1"></script>

  <style type="text/css">
    #content { 
      width400pxheight350pxpadding5pxoverflowauto;
      bordersolid 2px #AAAAAAbackground-color#FFFFFF;
      -moz-border-radius5px-webkit-border-radius5px-o-border-radius5pxborder-radius5px;
      -moz-box-shadow0 0 0.5em black-webkit-box-shadow0 0 0.5em black-o-box-shadow0 0 0.5em blackbox-shadow0 0 0.5em black;
    }
    .failure { colorred; }
    #status { font-size12px; }
  </style>
 
  <script type="text/javascript">
    dojo.require("esri.map");
    dojo.require("dojox.widget.Toaster");

    
    dojo.addOnLoad(function() {
      dojo.byId("url").value = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer/project?inSR=4326&outSR=102113&geometries=%7B%0D%0A%22geometryType%22%3A%22esriGeometryPoint%22%2C%0D%0A%22geometries%22%3A%5B%7B%22x%22%3A-117%2C%22y%22%3A34%7D%5D%0D%0A%7D&f=json";
      dojo.byId("content").value = "";
      
    });
    
    function getContent() {
      dojo.byId("content").value = "";
      dojo.removeClass(dojo.byId("content"), "failure");
      dojo.byId("status").innerHTML = "Downloading...";
      
      var url = esri.urlToObject(dojo.byId("url").value);
      
 
      var requestHandle = esri.request({
        url: url.path,
        content: url.query,
        callbackParamName: "callback",
        load: requestSucceeded,
        error: requestFailed
      },{useProxy:false});
      
    }
    
    function requestSucceeded(response, io) {
      console.log("Succeeded: ", response);
      requestCompleted();
      dojo.publish("msgTopic", [{
          message: "Succeeded!",
          type: "message",
          duration: 1000
      }]);      

      // dojo.toJson method converts the given JavaScript object
      // and its properties and values into simple text.
      dojo.toJsonIndentStr = "  ";
      dojo.byId("content").value = dojo.toJson(response, true);
      
 
    }
    
    function requestFailed(error, io) {
      console.log("Failed: ", error);
      dojo.addClass(dojo.byId("content"), "failure");
      requestCompleted();
      dojo.publish("msgTopic", [{
          message: "'Error!",
          type: "error",
          duration: 500
      }]); 
      dojo.toJsonIndentStr = "  ";
      dojo.byId("content").value = dojo.toJson(error, true);      
    }
    
    function requestCompleted() {
      dojo.byId("status").innerHTML = "Done.";
      var reset = function() {
        dojo.byId("status").innerHTML = "";
      };
      setTimeout(reset, 2000);
    }
    
  </script>
</head>
 
<body style="font-familyArial Unicode MS,Arial,sans-serif;">
  <p>
    Use esri.request to download content available in <b>JSONP</b> format.
  </p>
 
  <p>
    Enter URL here: <input type="text" id="url" size="75" />
    <input type="button" value="GO" onclick="getContent();" />
 
    <span id="status"></span>
  </p>
  
  <p>
    <p>Content:</p>
    <textarea id="content"></textarea>
  </p>
  <div dojoType="dojox.widget.Toaster" id="msgToaster" positionDirection="br-left" messageTopic = "msgTopic"></div>
</body>
</html>