Yoan Blanc’s weblog

Another lost swiss guy

Playing with Middlewares

Yoan BlancSat, 31 Jan 2009, , , , ,

What makes PHP great and easy is that hosting it is a piece of cake. Put a file named .php and you’re done. This is why it has a global impact into web development and remains a good starting point for new comers.

When you try to move out of it, using the populars Python or Ruby, it starts to get complicated. If you aren’t goint with the CGI-way because it appears to be too old school, you’ll start asking you this question: “Which framework do I have to pick?”

But start asking yourself: “What is a web framework?”. The visible parts of it are generally a templating system, an abstraction layer with a database (aka ORM or sometimes DAO) and a simple way to glue everything together mapping an URL to a function That is what you see and what you don’t see is the layer between the web server and your application. Most of the time, "print" goes into stdout (in the console) and not on the page itself. For Python and using Apache, you have different solutions. Some of them:

  • CGI, the old one;
  • FastCGI, a better alternative;
  • mod_python
  • mod_wsgi

The last one get a lot of attention, because of its simplicity and powerfulness. It’s basically an interface that your application has to respect. Here is a python example:

def application(environ, start_response):
  start_response("200 OK", [("Content-Type", "text/plain")])
  return ["Hello, world!"]

Giving this file to mod_wsgi will get you the famous Hello World. environ contains the environment variables, similar to the PHP’s $_ENV.

This is was for the simplicity, now let’s explore the powerfulness. This API enables to chain, pipe applications. You can insert, between the server and the real application, middlewares. So middleware are applications that will act like an application to the server and like a server to the application. An example with a ROT13 encoding middleware:

def rot13(app):
  def new_app(environ, start_response):
    response = app(environ, start_response)
    return [line.encode("rot13") for line in response]
  return new_app

application = rot13(application)

Now, application will call the original one and perform a rot13 encoding to its output. That’s it, this is a basic middleware. What is great about that is that any middleware is easily reusable, you can chaim them the way you need. Reusable means that there are a lot of WSGI Middlewares available you can reuse directly.

Using functions cannot be enough for you, this is why you’ll meet a lot of functors more than functions. The same middleware written using a functor:

class Rot13Middleware(object):
  def __init__(self, app):
    self.app = app
  
  def __call__(self, environ, start_response):
    response = self.app(environ, start_response)
    return [line.encode("rot13") for line in response]

application = new Rot13Middleware(app)

The application will return the original message now because doing rot13 two times is the identify function. A functor is basically a class that can act like a function.

WSGI is so brilliant that he made little brothers, Rack in Ruby:

app = lambda do |env|
  [200,
   {’Content-Type’ => ’text/plain’},
   "Hello, world!"]
end

def rot13decorator(func)
  lambda do |env|
    status, headers, response = func.call(env)
    [status, headers, response.tr("A-Ma-mN-Zn-z", "N-Zn-zA-Ma-m")]
  end
end

app = rot13decorator(app)

which inspires Jack using JavaScript (via Rhino and Jetty):

function app(env) {
  return [200,
          {"Content-Type": "text/plain"},
          "Hello, world!"];
}

function rot13decorator(func) {
  return function(env) {
    var response = func.invoke(env);
    return [response[0], response[1], rot13(response[2])];
  }
}

app = rot13decorator(app)

Both Ruby and Javascript have an object-oriented way of doing applications and middlewares too. See the full code on Github.

This to say that using a tool (call it framework) that follows such an interface will enable you to reuse existing middlewares easily. For example Rack has a JSONP middleware. JSONP means adding a function call around a JSON object, this is the kind of simple, reusable things that can be done via a middleware.

I loved LEGO®’s as a kid, and still have stars in my eyes when I see small toys I can play with and build everything my crazy mind might come up with.

Un point qui a contribué grandement à l’ampleur et le succès que connait PHP aujourd’hui est sans conteste lié à la simplicité de son déploiement. Un fichier .php, <?php et ça roule ma poule. Il reste selon moi un excellent point d’entrée pour un débutant.

Mais dès que la hype internet commence à envahir tes petits doigts boudinés, l’envie de tâter du Python ou Ruby va amener son lot de questions insolubles. « Quel framework dois-je choisir ? » avant de demander à mon seigneur Google son avis, posez-vous cette question-ci : « Qu’est qu’un framework web ? » Non, une secte ne fait pas partie des bonnes réponses, malgré ce qu’on pourrait penser lorsqu’on passe trop de temps sur Reddit.

Un web framework se compose généralement d’un système de templating (parfois nommé modèle HTML), d’une couche d’abstraction pour la base de donnée (ORM ou DAO), un système de mappage entre URLs et appel de fonction qui sert de glue à tout le système.

Ceci reste la partie visible, celle avec laquelle un développeur est amené à intéragir. Il reste une dernière zone, la couche entre le serveur web et l’application elle-même. Car comme vous avez pu le remarquer, il est rare qu’un simple print produise le même type de résultat qu’en PHP dans un de ses framework. Pour Python, avec Apache, il y a plusieurs moyen de déployer du code PHP :

  • en CGI ;
  • en FastCGI ;
  • via mod_python ;
  • ou mod_wsgi.

Les deux premiers ne sont pas propres à Python et vont donc fonctionner pour tous types d’applications. Le dernier gagne en popularité de part simplicité et puissance. WSGI est une interface qui définit : comment une requête HTTP se traduit en un appel Python et comment la réponse à cet appel va être pouvoir être envoyée au client. Ayant écrit, du code Python dans la partie anglophone de l’article, je vais publier ici le code Ruby nécessitant Rack. Rack est directement inspiré de WSGI et bénéficie de quelques fonctionnalité du langage Ruby au passage.

#!/usr/bin/env rackup

app = lambda do |env|
  [200,
   {"Content-Type" => "text/plain"},
   "Hello, world!"]
end

run app

C’est tout ce qu’il est nécessaire. env est l’équivalent des variables d’environnement en CGI, le $_ENV de PHP. Maintenant la force de ceci est la possibilité d’insérer des éléments entre une application et un serveur pour modifier à la volée ce qui s’y passe, en entrée comme en sortie. Ça s’appelle un middleware, et l’analogie utilisée est un tube, une série de tube ou un oignon qu’on doit traverser de l’extérieur vers l’intérieur pour en ressortir en retraversant les couches de l’intérieur vers l’extérieur. Trop de mots ici, place à l’action:

def rot13decorator(func)
  lambda do |env|
    status, headers, response = func.call(env)
    [status, headers, response.tr("A-Ma-mN-Zn-z", "N-Zn-zA-Ma-m")]
  end
end

run rot13decorator(app)

Ce middleware va encrypter la sortie en se servant de l’algorithme nommé ROT-13 (rotation des lettres de 13 position, opération qui appliquer deux fois consécutivement correspond à la fonction identité). C’est un simple décorateur dans le monde Python. La syntaxe fonctionnelle n’étant parfois pas suffisante, il est possible d’obtenir le même résultat en passant par des classes :

class Rot13Middleware
  def initialize(app)
    @app = app
  end
    
  def call(env)
    status, headers, response = @app.call(env)
    [status, headers, response.tr("A-Ma-mN-Zn-z", "N-Zn-zA-Ma-m")]
  end
end

Ceci ce nomme un foncteur, un objet pouvant agir comme une fonction (réalisé avec la méthode call en Ruby).

Récemment un projet nommé Jack a montré le bout de son nez. Il a pour but d’adapter Rack au monde Javascript en se servant de Rhino comme interpréteur JavaScript et Jetty comme serveur web. WSGI, Rack et Jack resposent sur la même idée, la même interface simple et puissante. Trouvez sur Github des exemples de code dans les trois langages.

L’intérêt des middlewares n’est peut-être pas criant au premier coup d’œil. Le fait que bon nombre de frameworks la supporte est signe de son utilité mais ne prouve toujours rien. Pour faire simple, une même interface permet une meilleure réutilisation d’outils existant. Repoze.profile offre un moyen basé sur WSGI d’effectuer du profiling d’applications. Et la nature de WSGI fait que c’est totalement indépendant du framework choisi.

Tout ça pour offrir un petit changement de paradigme dans toutes ces applications bien mono-blocs, bien carrées avec des pièces de LEGO©’s pouvant être insérées à l’envie, au besoin.

About

meYoan Blanc is a web developer that lives in Norway (Markveien 24, 0554 Oslo) works for Opera and comes from La Chaux-de-Fonds. This web site is for this weblog, a playground for random stuffs and can help keeping me connected with some friends out there.

Get my vCard or contact me by phone (skype:yoan.blanc) or email ().

Misc

RSS, list.blogug.ch

This site reflects only my opinion and is not affiliated with anyone else.

copyright 2006-2009 — doSimple.ch