cyberendroit.net
Implémenter un flux Atom
Publié le 7 décembre 2022 à 18:19

Contexte

Le site que vous consultez est réalisé grâce à Sveltekit, un framework (encore) permettant de réaliser ses applications web, sans DOM virtuel, et permet le SSR. Sur ce site, j’ai envie que les personnes qui utilisent des flux RSS afin de se tenir au courant de ce qui les intéresse, puisse se connecter à mon site internet, et en voir les activités récentes !

C’est donc pour ceci que nous allons implémenter nous même un flux Atom, qui puisse tenir tout le monde au courant de l’actualité du site !

Réalisation

Atom qu’est-ce que c’est ?

Atom est un format basé sur XML permettant aux sites internet de renseigner leur contenu. Il permet une autre interface avec le site internet, et permet aux agrégateurs de flux (Feedly, FreshRSS..) de le contacter afin de savoir ce qu’il contient.

La structure d’un fichier Atom

La racine

La structure d’un fichier Atom est assez basique, nous en verrons les bases afin d’implémenter juste ce qui nous intéresse. Voici la structure de base.

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    ...
</feed>

Il s’agit principalement d’une entête XML, ainsi qu’une balise feed renseignant tous les deux les spécifications du format Atom. Voyons comment remplir son contenu.

Définition de l’origine du flux

Afin de renseigner l’utilisateur sur ce que contient notre site internet pour son client, trois éléments sont obligatoires :

Attribut Description
id C’est un identifiant unique du flux. Possible d’utiliser des uuid, ou bien son nom de domaine.
title Il s’agit du titre du flux, qui sera visible pour différencier votre flux des autres pour l’utilisateur
updated IL s’agit de la date au format ISO de la dernière mise à jour contenue dans le flux. Elle est utilisée afin que le client ATOM sache si une mise à jour a déjà eu lieu

A ceux-ci, nous y rajouterons quelques éléments recommandés par la spécification :

Attribut Description
author Contient des information sur l’auteur.ice du site internet. Cet élément peut y contenir plusieurs présisions, comme son name, son email ou même son uri. Plus d’informations disponibles ici.
link Contient un lien vers le site internet?

Nous nous retrouvons donc avec un flux comme celui ci :

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>cyberendroit</title>
    <link href="https://cyberendroit.net/"/>
    <updated>2022-12-07T18:30:00Z</updated>
    <author>
        <name>pacha</name>
        <email>pacha@cyberendroit.net</email>
        <uri>https://cyberendroit.net/moi</uri>
    </author>
    <id>https://cyberendroit.net</id>
</feed>

D’autres éléments optionnels sont disponibles afin de spécifier davantage notre flux. Vous pouvez les consulter ici

Mais pour le moment c’est très bien, et il est l’heure d’y renseigner notre contenu, à l’aide de multiples balises entry.

Définition des entry

Une entry représente un contenu sur votre site internet. Quelques éléments y sont obligatoires :

Attribut Description
id Identifiant unique de la ressource. Pareillement que précédemment, il peut prendre la forme d’un UUID ou de l’URL vers l’entrée
title Le titre de l’entrée
updated La date de mise à jour de l’entrée

Et cet entrée, en plus de ces éléments, contenir une manière de présenter son contenu. Plusieurs manières sont possibles :

Attribut Description
link Contient le lien relié à l’entrée. S’il n’y a pas de content fourni, il est obligatoire
summary Contient un résumé de l’article.
content Contient le contenu de l’entrée ou des liens y menant. Obligatoire si le summary ou le link ne sont pas fournis.

Si notre site internet ne contenait qu’un seul article, voici quelle serait sa structure :

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

    <title>cyberendroit</title>
    <link href="https://cyberendroit.net/"/>
    <updated>2022-12-07T18:30:00Z</updated>
    <author>
        <name>pacha</name>
        <email>pacha@cyberendroit.net</email>
        <uri>https://cyberendroit.net/moi</uri>
    </author>
    <id>https://cyberendroit.net</id>
    <entry>
        <title>Rust c'est bon mangez-en</title>
        <link href="http://cyberendroit.net/articles/miam-rust"/>
        <id>http://cyberendroit.net/articles/miam-rust</id>
        <updated>2022-12-07T18:30:00Z</updated>
        <summary>Un article d'évangélisation de Rust</summary>
    </entry>
</feed>

Nous avons implémenté à la main notre flux Atom, génial ! Si vous souhaitez plus d’informations sur les spécifications du protocole, je vous invite à vous rendre sur sa spécification ici.

Il est l’heure de rendre la génération du flux automatique.

Génération d’un flux Atom à partir de Sveltekit

Création de la route

Nous allons tout d’abord créer la route qui mène à notre flux. Nous allons donc :

  • Créer un dossier src/routes/atom.xml
  • Y créer un fichier +server.ts.

Pour rappel, la création de ce fichier dans ce dossier nous permet de spécifier du code à exécuter côté serveur du code lorsque nous accédons à la ressource https://cyberendroit.net/atom.xml.

🤔
Est-ce que cela veut dire que nous allons parcourir tous nos articles à chaque fois que quelqu'un consulte notre flux ?

Eh bien non, et c’est ça qui est bon avec Svelte. Car au début de notre fichier +server.ts, Sveltekit nous permet de renseigner ceci :

export const prerender = true;

Qui nous permet de mettre en cache le contenu de la route après la première consultation.

💻
Cela permettra également à la route d'être générée statiquement si vous voulez générer le code statiquement.

Nous remplissons ensuite de quoi répondre simplement à la requête :

export async function GET() {
    const body = '';

    return new Response(body, {
        headers : {
            'Cache-Control': 'max-age=0, s-maxage=3600',
            'Content-Type': 'application/xml',
        }
    });
}

Et voilà ! Voyons désormais comment construire notre flux.

Remplir le flux de notre contenu

Cherchons notre contenu depuis notre endpoint :

const body = await generateFeed()

Et allons concevoir notre fonction pour tout d’abord retourner les détails de notre flux.

Détails du flux

Voici le minimum vital afin de remplir notre flux Atom, sans son contenu.

export async function generateFeed(): Promise<string> {
    const activities = await loadActivity()

    return `
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>cyberendroit</title>
    <link href="https://cyberendroit.net/"/>
    <updated>${activities[0]?.date.toISOString()}</updated>
    <author>
		<name>pacha</name>
	</author>
	<id>https://cyberendroit.net</id>
</feed>
`
}

Afin de parvenir à nos fins, nous utilisons une template string afin de remplir dynamiquement notre flux. Nous y avons spécifié tous nos besoins.

Petite attention pour le contenu de updated. Si vous vous souvenez bien, la valeur doit contenir la date à laquelle le flux a été mis à jour. Et, pour nous, doit donc correspondre à la date de mise à jour la plus récente de notre contenu. La méthode loadActivity doit donc trier notre contenu du plus récent au plus ancien.

Détails du contenu

D’abord, concentrons nous sur ce que doit contenir notre objet représentant une activité. Sur mon blog, j’ai plusieurs catégories de contenu. Il peut y avoir des articles, des éditos et pourquoi pas un jour des brèves ou des suites d’articles.

Mais tout ce contenu doit se mettre sous la même bannière de l’Activity. Voici le contenu de notre interface :

enum ActivityType {
    ARTICLE, EDITO     // Les types de contenu
}

interface Activity {
    title: string,      // Le titre de notre contenu
    summary: string,    // Sa description
    url: string,        // L'URL à laquelle le contenu peut être joint
    type: ActivityType, // Le type du contenu
    date: Date,         // La date de mise à jour
    tags: string[]      // Les thèmes du contenu
}

Remplissons désormais notre flux avec notre contenu.

export async function generateFeed(): Promise<string> {
    const activities = await loadActivity()

    return `
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>cyberendroit</title>
    <link href="https://cyberendroit.net/"/>
    <updated>${activities[0]?.date.toISOString()}</updated>
    <author>
		<name>pacha</name>
	</author>
	<id>https://cyberendroit.net</id>
	${activities.map(activity => {
    return `
            <entry>
				<title>${activity.title}</title>
				<link href="${activity.url}"/>
				<id>${activity.url}</id>
				<updated>${activity.date.toISOString()}</updated>
				<summary>${activity.summary}</summary>
				<category term="${activity.type.toString()}" />
				${activity.tags.map(tag => `
					<category term="${tag}" />
				`).join('')}
			</entry>
		`}).join('')}
</feed>`}

Rien de très nouveau ici si vous connaissez TypeScript :

  • Nous générons une entry par activité retournée
  • Nous remplissons tous les champs nécessaires à l’entry
  • Nous faisons une category par thème.

Il ne reste plus qu’à construire nos activités.

Chercher notre contenu

Parenthèse sur le Markdown

Mes articles (dont celui ci) sont rédigés selon le format Markdown. Le titre, la description, les tags, ils contiennent absolument toutes leurs informations dans ce qui s’appelle le frontmatter.

Le frontmatter est une entête dans laquelle vous pouvez définir les métadonnées d’un fichier Markdown. Voici un exemple d’article que je peux intégrer à mon site :

---
title: Implémenter un flux Atom
description: Voyons voir comment générer un flux Atom avec Sveltekit, pour tenir vos lecteur.ices informé.es.
tags: dev,javascript,svelte
published: true
publication_date: 2022-12-07T20:19:14+02:00
---

Merci de votre accueil chaleueux !

C’est donc comme ceci que l’on peut renseigner les métadonnées d’un article, et de cette manière que nous irons chercher les informations pour remplir notre objet Activity.

Créer nos objets d’activité

Voici notre fonction qui nous permet d’obtenir notre contenu : +

export async function loadActivity(): Promise<Activity[]> {
    const activitiesArticles = loadArticles().then(
        articles => articles.map(articleToActivity)
    );

    const activitiesEditos = loadEditos().then(
        editos => editos.map(editoToActivity)
    );

    let activities = await Promise.all([activitiesArticles, activitiesEditos]).then(a => a.flat());

    activities.sort((e1, e2) => e2.date.valueOf() - e1.date.valueOf())

    return activities
}

Sans rentrer dans les détails, voici ce que notre fonction fait :

  • loadArticles charge les métadonnées des articles. Nous transformons ces metadonnées en activités.
  • De même pour les éditos, nous les transformons en activités.
  • Nous exécutons ces transformations, et les trions du plus récent au plus ancien.

Et c’est tout ! Vous pouvez lancer votre application, vous avez un flux qui représente l’état de votre application même dans le cadre d’un blog statique !

Pour finir

Nous avons vu ensemble la structure d’un flux Atom, et appris comment l’implémenter à l’aide d’un blog sous Sveltekit. Si l’article vous a plu, n’hésitez pas à me le dire en commentaire (ça fait toujours plaisir), et à l’inverse si vous avez des remarques, ou des questions, même chemin, tout dans les commentaires.


Commentaires

loading comments..

Vous pouvez ajouter un commentaire en répondant sur Mastodon !
cyberendroit.net - développé avec amour par pacha