Des URL conviviales avec Flask

Amenez-en, des jolies URL!!!

18 avril 2013
2 commentaires

Depuis quelques temps, nous avons enrichi notre arsenal de technologies en y ajoutant Flask, un micro-cadre d'application web écrit en Python et basé sur Werkzeug (une bibliothèque WSGI) et Jinja2 (un moteur de substitution, ou template engine), tous deux très populaires dans le monde Python.

Flask se veut simple et extensible : il offre un minimum de services aux développeurs et fournit une structure pour lui ajouter des extensions (intégrations de bibliothèques externes) et des blueprints (morceaux d'application web plus ou moins autonomes).

Un des services offert par le cadre Flask est le routage, qui permet de déterminer le code devant être exécuté pour répondre à une requête HTTP. Par exemple, si notre application présente des documents aux visiteurs, on pourrait écrire :

def view_document(doc_id):
      """Charge le document doc_id de la base de données et 
      affiche sa représentation HTML."""
      doc = db.load(doc_id)
      html = render(doc)
      return html

# Créer notre application web.
app = Flask(import_name='my_app')
# Créer une règle pour exécuter la fonction view_document()
# lorsqu'on reçoit une requête pour une URL de la forme 
# /view/<doc_id>, où <doc_id> représente un identifiant alphanumérique.
app.add_url_rule('/view/<doc_id>', endpoint='document/view', view_func=view_document)

Ce qui est intéressant ici, c'est que peu importe le nombre de documents qui se trouvent dans notre base de données, chacun d'eux possède maintenant une URL pour y accéder. Ce qui est moins intéressant, c'est que cette URL n'est pas très conviviale. Par exemple, si nous avons un document identifié par "516da928fc30ce2b098207ba", son URL sera http://www.example.com/view/516da928fc30ce2b098207ba. On préfère généralement des URL plus jolies et lisibles, comme http://www.example.com/catalogue/fruits-et-légumes/pommes/cortland : c'est plus sympatique pour le visiteur et les moteurs de recherche peuvent se servir des mots qui s'y retrouvent pour indexer notre document.

Flask, de concert avec Werkzeug, propose un mécanisme pour créer des règles spécifiques qui masquent la règle générale définie ci-haut. Il suffit de créer une nouvelle règle pour le même endpoint, en fournissant des valeurs pour les paramètres manquants :

app.add_url_rule('/catalogue/fruits-et-légumes/pommes/cortland', 
                  endpoint='document/view', view_func=view_document, 
                  defaults={'doc_id': '516da928fc30ce2b098207ba'})

L'avantage de cette façon de faire est sa grande simplicité. Toutefois, elle a quelques inconvénients:

  • il faut enregistrer une règle pour chacun de nos documents lors de la configuration de l'application Flask;
  • chaque règle devient une entrée dans une table d'URL, et cette table doit être parcourue pour pouvoir traiter chaque requête HTTP (dans le pire des cas, il faut tester toutes les règles de la table).

La méthode fonctionne bien si on a peu de documents, mais, d'après nos expérimentations, elle devient impraticable au-delà de 100 000 documents. L'application prend plusieurs minutes à démarrer et la mise en correspondance des URL nécessite de tester un grand nombre de règles. Il faut trouver une autre méthode.

Pour nos besoins, nous avons mis au point une méthode qui utilise la base de données pour mettre en correspondance l'URL conviviale (/catalogue/fruits-et-légumes/pommes/cortland) avec l'URL canonique (/view/516da928fc30ce2b098207ba).

Pour que Flask utilise notre table, nous interceptons les appels à Rule.match() et Rule.build() en dérivant une classe de Rule :

from werkzeug.routing import Rule

class NiceRule(Rule):
    """A Rule that gives the url map the opportunity to alter the 
    path before matching an URL, or after building an URL."""

    def match(self, path):
        path = self.map.to_canonical_url(path)
        return super(NiceRule, self).match(path)

    def build(self, values, append_unknown=True):
        (domain_part, url) = super(NiceRule, self).build(values, append_unknown)
        url = self.map.from_canonical_url(url)
        return domain_part, url

Cette classe se réfère à sa table d'URL pour convertir les URL conviviales en URL canoniques, et vice-versa. Comme la table d'URL utilisée dans Flask (la Map de Werkzeug) ne supporte pas cette opération, et comme Flask ne permet pas (simplement et efficacement) de remplacer sa table d'URL par une instance d'une classe qui supporterait cette opération, nous ajoutons les fonctions manquantes à la classe Map. Ce procédé est ce qu'on appelle affectueusement le monkey patching.

from werkzeug.urls import url_quote, url_unquote

class NiceFlask(Flask):
    # Remplacer la url_rule_class par notre propre classe.
    url_rule_class = NiceRule

    def __init__(self, *args, **kwargs):

        def to_canonical_url(myself, path):
            trimmed_path = path[1:]
            doc = myself.database.find_one(myself.niceurl_collection, 
                                           {'nice': trimmed_path})
            if doc:
                return '|'+url_unquote(doc.canonical, myself.charset)
            return path

        def from_canonical_url(myself, path):
            doc = myself.database.find_one(myself.niceurl_collection, 
                                           {'canonical': path})
            if doc:
                return url_quote(doc.nice, myself.charset, safe='/:|')
            return path

        # Ajouter les fonctions manquantes à la classe Map.
        Map.to_canonical_url = to_canonical_url
        Map.from_canonical_url = from_canonical_url

        super(NiceFlask, self).__init__(*args, **kwargs)

        # Connecter la map à notre base de donnée.
        self.url_map.database = DatabaseConnection()
        self.url_map.niceurl_collection = self.url_map.database.GetCollection('niceurl')

Nous avons maintenant une application dont la liste des URL se restreint à quelques URL canoniques et nous utilisons la base de données (et ses indexes rapides à consulter) pour faire efficacement la mise en correspondance des URL conviviales avec les URL canoniques. Le temps de démarrage de l'application est réduit à ce qu'il était auparavant, et le temps requis pour trouver la règle à exécuter demeure très décent (d'autant plus si on cache les résultats de la base de donnée en mémoire).

Ce n'est cependant pas la façon la plus efficace de mettre en œuvre les URL conviviales : en effet, pour trouver la règle qui peut répondre à une requête, Flask doit parcourir toutes les règles de sa table d'URL pour comparer l'URL demandée aux URL des règles. Puisque nous transformons l'URL demandée dans NiceRule, nous la transformons à chaque fois qu'une règle est vérifiée. Nous pourrions faire cette transformation une seule fois si nous la faisions dans la table des URL, avant de commencer l'itération sur les règles. À première vue cette idée semble difficile à réaliser à cause de la façon dont Flask et Werkzeug sont construits. Nous entendons toutefois l'explorer pour encore améliorer nos temps de réponse.

Ajouter aux favoris / partager

Commentaires

2014-04-11 03:20

Bonjour

Tout d'abord merci et 2 questions suite à la découverte de votre article:
- l'url re writing ainsi pratiqué améliore t il le référencement ?
- le module flask-babel (traduction) permet il lui aussi d'améliorer le référencement

... enfin plus généralement y a t il des endroits ou trouver de l'information sur le référencement et ce joli framework FLask ?
D'avance merci

2014-04-14 10:01

Il est difficile de savoir exactement comment les moteurs de recherche indexent les documents, mais il est généralement reconnu que ceux-ci tiennent compte de l'URL du document. Une URL qui contient des mots qui se retrouvent également dans le document ne peut que renforcer la pertinence de ces mots dans l'index.

Le module flask-babel vous permet de traduire l'interface de votre site dans la langue préférée de vos visiteurs. On peut donc dire qu'il améliore indirectement le référencement car votre site sera indexé dans la langue qui intéresse votre visiteur potentiel.

Vous pouvez trouver toute l'info à propos de flask sur flask.pocoo.org. Pour ce qui est du SEO, le web regorge de conseils. Il suffit de les mettre en pratique!

Soumettre un nouveau commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
  • Les adresses de pages web et de courriel sont transformées en liens automatiquement.
  • Balises HTML autorisées : <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Les lignes et les paragraphes vont à la ligne automatiquement.

Plus d'informations sur les options de formatage