Dans ce billet, j’aborde CORS et je parle des solutions pour utiliser des données provenant d’autres sites.
Le développement du web sémantique et de l’usage des données liées passe assurément par le développement de sites web qui exploitent des données rendues disponibles dans le cadre des technologies du web sémantique. L’exemple le plus connu consiste en l’usage de DBPedia pour compléter les contenus d’une page web (voir par exemple http://labs.sparna.fr/dbpedia-enrich-news-article.html).
Mais, utiliser DBPedia depuis une page Web, cela suppose d’interroger un point d’accès SPARQL (en, fr) de DBPedia depuis la page, puis d’utiliser, pour enrichir la page, les données obtenues. De façon évidente cela nécessite de contourner la règle de sécurité qui empêche d’utiliser dans une page web un contenu provenant d’un autre serveur que le serveur d’où provient la page (sauf si c’est un contenu particulier comme un code javascript ou une image jpeg): le principe CORS. Je nommerais par la suite le serveur d’où vient la page ‘serveur source’ , et le serveur d’où on cherche à récupérer des données ‘serveur étranger’.
J’ai lu beaucoup de choses sur CORS avant de comprendre une chose essentielle: pour contourner CORS, le serveur qui fournit les données doit mettre en oeuvre des solutions spécifiques; donc, vous ne pouvez pas utiliser n’importe quelle source, mais une source prête à coopérer avec vous. Nous verrons plus loin qu’une des solutions permet d’exploiter n’importe quelle source, mais via votre serveur qui devient la source de données pour la page Web.
Trois solutions sont connues pour interroger une source étrangère à la page:
- la déclaration du serveur source sur le serveur étranger,
- l’envoi des données sous forme de code javascript (JSONP),
- l’acheminement des données étrangères via le serveur source (proxy)
Enregistrement sur un serveur de données
Dans ce cas-là, le serveur source a obtenu du serveur étranger que ce dernier enregistre le serveur source dans une liste de demandeurs autorisés.
Une fois cet enregistrement effectué, une page qui vient du serveur source va pouvoir émettre des requêtes vers le serveur étranger.
Il y a donc un préalable fort: les responsables des deux serveurs doivent avoir été en contact et le serveur de données doit avoir référencé l’autre serveur. C’est surement la solution la meilleure en terme de sécurité, mais elle est très contraignante. En particulier, il y a un délai et une grosse incertitude entre le moment où vous identifiez une source de données que vous voudriez utiliser et le moment où vous pourrez utiliser cette source (si elle vous le permet).
La façon de faire cet enregistrement dépend complètement de la nature du serveur étranger. Par exemple, j’administre shadok, un serveur Virtuoso (shadok.enst.fr/sparql) et j’ai pu y déclarer un serveur (givingsense.eu) qui va héberger des pages qui pourront faire des requêtes SPARQL sur le serveur shadok. Pour cela, j’ai ajouté http://givingsense.eu dans la liste des serveurs acceptés par shadok, en suivant les indications trouvées ici.
Un exemple élémentaire d’utilisation est visible en suivant ce lien:
http://givingsense.eu/testapp/testcors.html
JSONP
L’idée ici est simple: puisque les pages peuvent charger du javascript, envoyons leur du javascript: le serveur de données va envoyer un appel de fonction qui reçoit les données en paramètre. La page source doit définir le code d’exécution de la fonction. Cette solution est connue sous le nom de JSONP.
La page contient par exemple la définition de fonction
function myjsonp(data) { // défini la fonction callback dont le nom est passé en paramètre de la requête queryAnswer = data; }
L’URL suivante, qu’on peut récupérer en faisant un test directement sur l’interface de dbpedia.org/sparql
http://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&query=select+distinct+%3FConcept+where+%7B%5B%5D+a+%3FConcept%7D+LIMIT+5&format=application%2Fsparql-results%2Bjson
donne pour résultat
{ "head": { "link": [], "vars": ["Concept"] }, "results": { "distinct": false, "ordered": true, "bindings": [ { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Image" }}, { "Concept": { "type": "uri", "value": "http://www.w3.org/2002/07/owl#Thing" }}, { "Concept": { "type": "uri", "value": "http://xmlns.com/foaf/0.1/Person" }}, { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Person" }}, { "Concept": { "type": "uri", "value": "http://www.ontologydesignpatterns.org/ont/dul/DUL.owl#Agent" }} ] } }
Mais si on fait cette requête dans une page web, elle va échouer du fait qu’elle va chercher des données sur un serveur différent de l’origine de la page.
Ajoutons à la requête ci-dessus le texte suivant
&callback=myjsonp
qui définit le paramètre callback et lui attribue la valeur myjsonp (ou tous nom que vous aurez donné à votre fonction callback). Le serveur Virtuoso, qui héberge DBPedia, va utiliser ce paramètre pour encapsuler la réponse dans un appel à la fonction myjsonp.
Cette requête pourra être exécutée si elle est interprétée par le navigateur comme étant le chargement de code javascript:
<script type= »text/javascript » src= »…ici la requête définie ci-dessus… »></script>
Ainsi, lorsqu’il interprète cette ligne, le navigateur reçoit une fonction javascript, qu’il exécute; tel que notre fonction est définie, le résultat de la requête sparql est ainsi stocké dans la variable globale queryAnswer.
Le serveur étranger -ici Virtuoso- a du mettre en oeuvre un traitement de requête spécifique qui, au lieu de renvoyer les données, renvoie les données encapsulées dans un appel de fonction.demande des données.
Ainsi, pour DBPedia, si vous demandez d’avoir une réponse de type JSON et que vous ajoutez le paramètre callback, vous obtenez du JSONP. Pour la requête ci-dessus, le résultat reçu est:
myjsonp( { "head": { "link": [], "vars": ["Concept"] }, "results": { "distinct": false, "ordered": true, "bindings": [ { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Image" }}, { "Concept": { "type": "uri", "value": "http://www.w3.org/2002/07/owl#Thing" }}, { "Concept": { "type": "uri", "value": "http://xmlns.com/foaf/0.1/Person" }}, { "Concept": { "type": "uri", "value": "http://dbpedia.org/ontology/Person" }}, { "Concept": { "type": "uri", "value": "http://www.ontologydesignpatterns.org/ont/dul/DUL.owl#Agent" }} ] } })
On voit que c’est la même chose que précédemment entouré par myjsonp(…).
Un exemple est visible ici:
http://givingsense.eu/testapp/dbpediaJsonp.html
Pour obtenir un comportement dynamique, il faudra créer par code des balises script et les injecter dans la page.
Proxy
La dernière possibilité que je vais présenter est l’usage d’un proxy.
Puisque le serveur qui envoie des données doit avoir confiance dans la page qui les lui demande, une solution consiste à demander ces données au serveur qui a fourni la page.
Dans ce cas, la page envoie sa requête -par exemple, celle définie à la section précédente- comme paramètre d’un service implémenté sur le serveur source. Le service du serveur source récupère ce paramètre, exécute la requête vers le serveur étranger, reçoit le résultat qu’il renvoie à la page.
Bien sur cette solution a des inconvénients:
- elle ajoute une charge de traitement sur le serveur source,
- elle nécessite un transfert des données du serveur étranger vers le serveur source, puis du serveur source vers la page qui a émis la requête,
- elle ajoute ainsi de la latence.
Mais, elle a aussi des avantages:
- elle permet d’interroger toutes sources de données,
- au prix d’une implémentation sous forme de proxy-cache, elle diminue la dépendance aux aléas de fonctionnement du serveur étranger, et, même, dans ce cas, la latence peut être diminuée (dans l’exemple de la requête ci-dessus, le code sparql ne serait exécuté qu’une fois sur le serveur étranger, puis le résultat stocké sur le serveur source; les exécutions suivantes de la même requête obtiendraient directement le résultat du cache sur le serveur source).
Je donnerai des éléments dans un prochain billet pour la mise en oeuvre de cette solution.
Pour aller plus loin
Vous trouverez une présentation plus approfondie des appels CORS ici
http://www.html5rocks.com/en/tutorials/cors/
Le support de JSONP par les appels Ajax proposés par jQuery
http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
où on peut noter en particulier que jQuery convertit les requêtes de données json en requêtes jsonp lorsque la requête s’adresse à une origine différente que celle de la page courante.
Quelques compléments sur le fonctionnement de CORS:
http://margaine.com/2014/06/28/jsonp-vs-cors.html
En conclusion
Vous aurez surement besoin de la solution via le proxy pour certaines sources de données qui n’offrent aucune des deux solutions précédentes, donc autant en équiper votre serveur dès maintenant. Nous verrons des façons de le faire dans un prochain billet.
Vous verrez que de plus en plus de sources vous offriront la solution JSONP; elle est assez facile à mettre en oeuvre et donnera surement plus de dynamicité à vos pages.
Enfin, la solution déclarative est limitée aux sources de données sur lesquelles vous pouvez intervenir pour paramétrage (ou quelqu’un peut le faire pour vous): ce sera surement le cas le moins courant. S’il est possible, rappelez-vous que c’est quand même le plus sûr.
[wl_chord]
[wl_navigator]
[wl_faceted_search]
Ping : WordLift est arrivé sur ce site | Objets Numériques et Sémantique