Urls, ressources et représentations : exemple des flux RSS/Atom

Une url devrait pouvoir identifier une ressource sans préjugé de la représentation que l'on souhaite récupéré (html, xml, json, etc.) ; Le protocole HTTP le permet : c'est ce qu'on appelle la négotiation de contenu. Mais dans la pratique, c'est un peu plus compliqué.

Prenons l'exemple d'un site web qui possède une page d'accueil en HTML qui liste les derniers documents publiés et un flux de mises à jour du site au format Atom. Pour obtenir la page d'accueil, nous utiliserions une requête simple :

GET / HTTP/1.1
Host: example.org
première requête HTTP, effectuée avec n'importe quel navigateur web.

Pour récupérer le flux de mises à jour, nous pourrions utiliser la même url mais en précisant que nous souhaitons recevoir la réponse au format Atom grâce à l'en-tête HTTP « Accept » :

GET / HTTP/1.1
Host: example.org
Accept: application/atom+xml
Seconde requête HTTP, précisant le format de réponse accepté.

Nous avons donc une url (« http://example.org ») qui retourne une ressource (« les derniers documents publiés ») dont la représentation (html, atom) dépendra du format demandé par la requête. Illustrons cette possibilité avec un script PHP qui, en fonction de l'en-tête HTTP reçut lors d'une requête, affichera soit une page HTML, soit un flux atom (Sources).

  • example.org
    • feed.atom
    • index.php
    • welcome.html
organisation du projet.
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <title>Example.org website</title>
    <id>feed:example.org</id>
    <link href="http://example.org" rel="alternate" type="text/html" />
    <updated>2013-01-11T21:00</updated>
    <author>
        <name>Marcel Dupont</name>
        <uri>http://example.org</uri>
        <email>mdupont@example.org</email>
    </author>
    <entry>
        <id>feed:example.org,hello</id>
        <link href="http://example.org/hello" rel="alternate" type="text/html" />
        <title>Welcome</title>
        <published>2013-01-11T21:00</published>
        <updated>2013-01-11T21:00</updated>
        <summary>Hello World !</summary>
    </entry>
    <entry>
        <id>feed:example.org,first</id>
        <link href="http://example.org/first" rel="alternate" type="text/html" />
        <title>First post</title>
        <published>2013-01-11T22:00</published>
        <updated>2013-01-11T22:00</updated>
        <summary>This is the first post</summary>
    </entry>
</feed>
Exemple de flux Atom (feed.atom).
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Example.org website</title>
    <link rel="alternate" type="application/atom+xml" href="index.php">
</head>
<body>
    <h1>Accueil</h1>
    <article>
        <h2>Welcome</h2>
        <p>Publié le <time datetime="2013-01-11T21:00">11 janvier 2013 à 21:00</time></p>
        <p>Hello World !</p>
    </article>
    <article>
        <h2>First post</h2>
        <p>Publié le <time datetime="2013-01-11T22:00">11 janvier 2013 à 22:00</time></p>
        <p>This is the first post</p>
    </article>
</body>
</html>
Exemple de page d'accueil (welcome.html).
<?php 
    $filename = 'welcome.html';
    if (strpos($_SERVER['HTTP_ACCEPT'], 'application/atom+xml') === 0) {
        header('Content-Type: application/atom+xml; charset:utf-8');
        $filename = 'feed.atom';
    }
    echo(file_get_contents($filename));
?>
Script PHP recevant la requête (index.php).

Si on ouvre la page « index.php » dans un navigateur web (première requête), le contenu HTML sera affiché ; mais si nous lançons l'url avec un outil permettant de préciser les en-têtes HTTP de la requête (l'extension RESTClient de Firefox par exemple), et donc d'effectuer la seconde requête, nous aurons en réponse le flux Atom.

Bien. Tout cela est bel et bon mais comment l'utiliser en pratique sur un site web ? Réponse courte : on ne peux pas. Pourquoi ? Parce que lorsqu'un navigateur web effectue une requête HTTP, un en-tête « Accept » est envoyé par défaut :

GET / HTTP/1.1
Host: example.org
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Première requête HTTP lancée dans un navigateur web, avec l'en-tête « Accept » envoyé par défaut (Firefox).

Toute requête partant du navigateur supposera donc que la réponse à recevoir sera préférentiellement du HTML ; même en précisant le type du contenu que l'on attend, dans un élément link ou un lien, cela ne changera rien.

<link rel="alternate" type="application/atom+xml" title="feed" href="http://example.org">
<!-- ou -->
<a href="http://example.org" type="application/atom+xml">Flux atom</a>
Définition du type de ressource pointée par un lien.

On aurait pu penser qu'en écrivant quelque chose comme ce qui précède, Le navigateur effectuerait une requête en s'appuyant sur la valeur de l'attribut « type » pour forcer / modifier l'en-tête « Accept » de la requête, mais non. Et c'est fort dommage ; j'aurai aimé construire sur ce site des flux par catégories de la manière suivante :

GET /cats/name HTTP/1.1
Host: omacronides.com
Requête de la catégorie « name » retournant une page HTML qui liste l'ensemble des documents publiés sur le site appartenant à la catégorie.
GET /cats/name HTTP/1.1
Host: omacronides.com
Accept: application/atom+xml
Requête de la catégorie « name » retournant un flux atom qui liste les n derniers documents publiés dans la catégorie.

Nous sommes donc obligé de créer des urls dédiées aux flux à partir du moment où l'on souhaite que celui-ci puisse être « atteignable » via un navigateur web. A titre d'exemple, voilà comment WordPress construit ces urls pour les catgéories :

GET /category/categoryname HTTP/1.1
Host: example.org
Page web de la catégorie « categoryname » dans WordPress.
GET /category/categoryname/feed HTTP/1.1
Host: example.org
Flux rss de la catégorie « categoryname » dans WordPress.

FIELDING, Roy ; GETTYS, James ; MOGUL, Jeffrey, et al.. Hypertext Transfer Protocol - HTTP/1.1. W3C, . 14.1 Header Field Definitions : Accept

FIELDING, Roy ; GETTYS, James ; MOGUL, Jeffrey, et al.. Hypertext Transfer Protocol - HTTP/1.1. W3C, . 12 Content Negotiation

Content negotiation. Mozilla Developer Network,

NIBAU, Rui. En-tête HTTP Accept en PHP. Omacronides,

WordPress Feeds. codex.wordpress.org