Date de publication du RFC : Mars 2012
Auteur(s) du RFC : J. Gregorio (Google), R. Fielding (Adobe), M. Hadley (MITRE), M. Nottingham (Rackspace), D. Orchard (Salesforce.com)
Chemin des normes
Première rédaction de cet article le 28 mars 2012
Beaucoup d'applications Web utilisent des URI (RFC 3986) qui peuvent être décrits par un gabarit (template), avec des variables qui seront incarnées pour produire l'URI final. Ce RFC, issu d'une coopération entre l'IETF et le W3C normalise un langage (déjà largement déployé) pour ces gabarits.
La section 1.1 du RFC fournit quelques exemples d'URI obéissant à un gabarit. Par exemple, des pages personnelles identifiées par le tilde :
http://example.com/~fred/ http://example.com/~mark/
ou des entrées d'un dictionaire :
http://example.com/dictionary/c/cat http://example.com/dictionary/d/dog
ou encore un moteur de recherche :
http://example.com/search?q=cat&lang=en http://example.com/search?q=chien&lang=fr
À chaque fois, le patron de conception sous-jacent est assez clair. Le langage décrit dans ce RFC 6570 permet d'abstraire ce patron dans un gabarit d'URI. Les trois exemples cités plus haut pourront être décrits :
http://example.com/~{username}/ http://example.com/dictionary/{term:1}/{term} http://example.com/search{?q,lang}
Cela permet d'abstraire un URI, en montrant clairement au lecteur le processus de construction. Cela contribue donc à la création de beaux URL, qui ne sont pas simplement des identificateurs opaques, mais qui peuvent être compris par l'utilisateur du Web.
En dehors de l'intérêt intellectuel, quelle est l'utilité de ce
système ? Il permet notamment la création automatique d'URI, à partir
de gabarits et de valeurs pour les variables (comme la variable
term
dans le deuxième exemple ci-dessus). Les
protocoles fondés sur REST en seront
ravis. Ainsi, le gabarit
http://www.example.com/foo{?query,number}
combiné
avec les variables {query: "mycelium", number: 100}
va donner
http://www.example.com/foo?query=mycelium&number=100
. Si
query
n'était pas défini, cela serait simplement http://www.example.com/foo?number=100
.
Pour prendre des exemples réels, on peut décrire les URL
d'OpenStreetMap comme tirés du gabarit
http://www.openstreetmap.org/?{lon,lat}
et ceux
de Wikipédia comme faits à partir de
http://fr.wikipedia.org/wiki/{topic}
.
La section 1.2 explique qu'il existe en
fait plusieurs niveaux pour le langage de description de gabarits. Une
mise en œuvre donnée s'arrête à un certain niveau, les premiers
étant les plus simples. Le
niveau 1 fournit simplement l'expansion des
variables (qui sont placées entre accolades), avec échappement. Si
var
vaut "Bonjour le monde", l'expansion de
{var}
sera
Bonjour%20le%20monde
.
Le niveau 2 ajoute un opérateur
+
pour les variables
pouvant contenir des caractères spéciaux, et le
#
pour les identificateurs
de fragment. Si path
vaut "/foo/bar",
{path}
est expansé en
%2Ffoo%2Fbar
alors que
{+path}
donnera /foo/bar
. Et
avec la variable var
valant "Bonjour le monde", l'expansion de
{#var}
sera
#Bonjour%20le%20monde
permettant de construire
des URI avec un identificateur pointant vers un point précis du
document. Par exemple, les URL des Internet-Drafts
dans la file d'attente de l'éditeur des RFC ont
le gabarit
http://www.rfc-editor.org/queue2.html{#draftname}
avec, par exemple, draftname
égal draft-ietf-lisp
.
Au niveau 3, cela devient bien plus riche : on
a plusieurs variables dans une expression (le gabarit
map?{x,y}
devient l'URI
map?1024,768
si x=1024 et y=768). Et on gagne
surtout les paramètres multiples (séparés par
&) si utilisées dans les
formulaires Web : le gabarit
{?x,y}
devient
?x=1024&y=768
.
Enfin, le niveau 4 ajoute des modificateurs
après le nom de la variable. Par exemple, :
permet d'indiquer un nombre limité de caractères (comme dans le
term:1
que j'ai utilisé au début, qui indique la
première lettre). Et le *
indique une variable
composite (une liste ou un
dictionnaire) qui doit être elle-même
expansée. Si la variable couleurs
vaut la liste
{rouge, bleu, vert}, le gabarit (de niveau 1)
{couleurs}
donnera
rouge,bleu,vert
alors que la gabarit de niveau 4
{?couleurs*}
vaudra
?couleurs=rouge&couleurs=bleu&couleurs=vert
.
Des mécanismes similaires à ces gabarits existent dans d'autres
langages comme OpenSearch ou
WSDL. Par exemple, le gabarit décrivant les URL
du moteur de recherche de mon blog a la
forme http://www.bortzmeyer.org/search{?pattern}
et, dans dans sa description
OpenSearch, on trouve la même information (quoique structurée différemment).
Comme l'explique la section 1.3, cette nouvelle norme vise à unifier le langage de description de gabarits entre les langages, tout en restant compatible avec les anciens systèmes. Normalement, la syntaxe des gabarits d'URI est triviale à analyser, tout en fournissant la puissance expressive nécessaire. Il s'agit non seulement de générer des URI à partir d'un patron, mais aussi de pouvoir capturer l'essence d'un URI, en exprimant clairement comment il est construit.
La section 2 décrit rigoureusement la syntaxe. En théorie, un analyseur qui ne gère que le niveau 1 est acceptable. Toutefois, le RFC recommande que tous les analyseurs comprennent la syntaxe de tous les niveaux (même s'ils ne savent pas faire l'expansion), ne serait-ce que pour produire des messages d'erreurs clairs (« This feature is only for Level 3 engines and I'm a poor Level 2 program. » .
Cette section contient donc la liste complète des caractères qui ont une signification spéciale pour le langage de gabarits, comme +, ? ou &, ainsi que de ceux qui sont réservés pour les futures extensions (comme = ou @). En revanche, $ et les parenthèses sont exprèssement laissés libres pour d'autres langages qui voudraient décrire une part de l'URI.
Ensuite, la section 3 se consacre aux détails du processus d'expansion qui,
partant d'un gabarit et d'un ensemble de valeurs, va produire un
URI. Notez qu'une variable qui n'a pas de valeur ne donne pas un 0 ou
un N/A
ou quoi que ce soit de ce genre. Elle
n'est pas expansée. Donc, le gabarit {?x,y}
avec
x valant 3 et y n'étant pas défini, devient
?x=3
et pas
?x=3&y=NULL
. Si x n'était pas défini non
plus, le résultat serait une chaîne vide (sans même le point
d'interrogation).
Est-ce que ces gabarits peuvent poser des problèmes de sécurité ? La section 4 regarde cette question et conclus que cela dépend de qui fournit le gabarit, qui fournit les variables, et comment va être utilisé l'URI résultant (aujourd'hui, les mises en œuvre existantes les utilisent dans des contextes très variés). Par exemple, des variables contenant du code JavaScript peuvent être dangereuses si l'URI résultant est traité par ce langage. Des attaques de type XSS sont donc parfaitement possibles si le programmeur ne fait pas attention.
Pour les programmeurs, l'annexe A contient un ensemble de recommandations sur la mise en œuvre de ce RFC, ainsi qu'un algorithme possible pour cette mise en œuvre. Il existe plusieurs mises en œuvre de ce RFC. Malheureusement, toutes celles que j'ai essayées semblent limitées au niveau 1 et ne le disent pas clairement.
Le code Python en http://code.google.com/p/uri-templates/
(niveau 1 et un bout du niveau 2) :
import uritemplate import simplejson import sys vars = {"foo": "bar/"} def test_print(template): actual = uritemplate.expand(template, vars) print actual test_print("http://www.example.org/thing/{foo}") test_print("http://www.example.org/thing/{+foo}")
On obtient :
http://www.example.org/thing/bar%2F http://www.example.org/thing/bar/
Avec le module Perl URI::Template
(niveau 1), on peut écrire :
use URI::Template; my $template = URI::Template->new( 'http://example.com/{foo}' ); my $uri = $template->process( foo => 'Hello World/' ); print $uri, "\n";
Le code Haskell en http://hackage.haskell.org/cgi-bin/hackage-scripts/package/uri-template
(niveau 1 seulement) écrit ainsi :
module Main(main) where import Network.URI.Template testEnv :: TemplateEnv testEnv = addToEnv "foo" "Hello World/" $ addToEnv "x" "1024" $ addToEnv "y" "768" $ newEnv main :: IO () main = do putStrLn (expand testEnv "http://example.org/{foo}") return ()
Donne :
% runhaskell Test.hs http://example.org/Hello%20World%2f
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)