February 2010

AF83 est fier de sortir ErrorNot. ErrorNot est un service web open source sous licence AGPLv3. Le but de ErrorNot est de gérer les exceptions levées par vos applications. ErrorNot s'inspire fortement de l'application Hoptoad.

J'ai ainsi fait découvrir ErrorNot ce matin au sein d'AF83 grâce à nos fameux ateliers du lundi. Vous pouvez trouver les slides sur http://showoff-errornot.heroku.com/.

Le code est disponible sur ErrorNot (github). Essayez-le et remontez-nous les bugs pour que nous puissions l'améliorer.

Plus de détails à suivre...

books

Javascript avancé

Vision Media a publié un ebook sur le javascript avancé. Il ne coûte que 4$ canadien (moins de 3€), mais j'ai appris pas mal de choses avec sur le langage javascript lui-même (pas le DOM). Ses 64 pages sont découpées en 6 chapitres :

  • Closures, Scope & Context
  • Prototypal Inheritance
  • Advanced Meta-programming Techniques
  • Behavior Driven Development with JSpec
  • Creating a jQuery Clone
  • Tools of the Trade

Si vous souhaitez mieux comprendre le javascript, pour l'utiliser avec Node.js par exemple, ça serait vraiment dommage de passer à coté.

http://www.dev-mag.com/2010/02/18/advanced-javascript/

Rails 3 Upgrade Handbook

Jeremy McAnally a écrit un ebook de 120 pages avec tout ce qu'il faut pour passer une application à Rails 3 (actuellement en beta). La plupart des informations sont disponibles ailleurs, sur le site officiel ou des blogs, mais il est très difficile d'avoir une vue synthétique de l'ensemble des modifications. Le prix de cet ebook, 12$, se justifie très simplement par le temps gagné. Vous aurez toutes les informations sous les yeux pour faire votre migration, et la liste des points à ne pas oublier est vraiment très pratique. Pour ma part, je ne regrette absolument pas cet achat.

http://www.railsupgradehandbook.com/

Contribuer en retour à l'Open Source

Ryan Bates a lancé une initiative qui va être, je l'espère, suivie par de nombreux développeurs. L'idée est simple : on utilise régulièrement des Logiciels Libres, notamment des plugins ou gems pour Ruby on Rails, et il est souhaitable de participer à cet effort. Pour cela, Ryan Bates propose une méthode :

  1. Trouver votre projet Rails le plus important
  2. Lister tous les plugins et gems que vous utilisez sur ce projet
  3. Pour chacun, faites une contribution avec, au choix :
    • Donner de l'argent
    • Corriger un bug
    • Ajouter de la documentation
    • Ou, tout simplement, remercier son auteur

Je compte sur vous pour faire la différence en faveur de ses plugins et gems Libres.

http://railscasts.com/give_back

Internet pipes

Le web évolue en permanence. Certaines tendances ne sont que des effets de mode passagers, tandis que d'autres transforment de manière profonde le web. En particulier, ces évolutions peuvent amener de nouvelles façons de concevoir et construire les sites web.

En ce moment, nous entendons de plus en plus parler de « web temps réel ». Cette dénomination est assez floue, mais derrière elle, on peut retrouver le fait que l'information navigue rapidement et relativement facilement entre le sites web : google va récupérer dans google buzz vos images en provenance de flickr et picasa, de nombreux sites intègrent des flux twitter, même ce blog récupère de l'information en provenance de github et de delicious.

Cette approche a des conséquences techniques non-négligeables. Les sites web ne sont plus juste un intermédiaire entre une base de données et des utilisateurs, mais sont de plus en plus obligés de se communiquer entre eux. Or, la communication entre deux sites web est un phénomène assez lent à cause de la latence. Cette lenteur est toute relative : en générale, c'est de l'ordre de quelques centièmes de secondes. Pourtant, cela a un impact énorme sur des applications web qui étaient jusque là capable de générer une page web en moins d'un dixième de seconde.

Les frameworks actuels comme Ruby on Rails ou Django reposent leurs capacité à encaisser un trafic conséquent sur cette rapidité, mais quand ils se retrouvent littéralement à piétiner en attendant que d'autres sites web répondent, cela peut rapidement devenir le drame : le site s'engorge puis finit par ne plus répondre.

La solution existe et est bien connue : il faut changer de paradigme de scalabilité au profit d'architectures asynchrones. L'idée est simple : quand un site web attend la réponse d'un autre site web, il va en profiter pour commencer à répondre à un deuxième client, puis à un troisième, un quatrième, etc. Il va alors se retrouver à gérer un certain nombres de requêtes en parallèle.

Ça ne paraît pas très compliqué, mais pour le développeur, c'est l'enfer : il doit savoir quand il peut traiter une requête, quand il doit passer la main, éviter à tout prix de mélanger les données de l'un avec celle de l'autre, etc. À l'heure actuelle, ce style de programmation est vraiment très exigeant, et une erreur est très vite arrivée.

Heureusement, des personnes ont décidées de s'attaquer à ce problème et de proposer des outils pour simplifier ça. Les frameworks Event-Driven commencent à apparaître, mais cela demande de mettre en place de nouvelles API, d'apprendre de nouveaux réflexes aux développeurs.

Par exemple, là où un simple tweets = Twitter::Base.new(credentials).user_timeline aurait suffit pour récupérer les tweets d'un utilisateur (en Ruby avec le gem twitter), il faut utiliser une approche bien plus compliquée dans un monde asynchrone (exemple libre) :

  1. var tweets = []
  2. new Twitter(credentials).addListener('tweet', function(tweet) {
  3.     tweets.push(tweet);
  4. }).addListener('close', function() {
  5.     // Faire quelque chose d'intéressant avec les tweets ici
  6. });

Comme on peut le voir, le code intéressant doit maintenant se trouver à l'intérieur de la fonction de callback passée à close. On va alors avoir tendance à imbriquer des fonctions dans d'autres fonctions, elles-mêmes imbriquées dans d'autres fonctions, et ainsi de suite. Au final, cela le rend le code difficilement lisible, et assez peu flexible.

Les créateurs de frameworks sont à la recherche de solutions pour proposer des API plus agréables à utiliser. EventMachine, coté Ruby, et Tornado, coté Python, essayent d'encapsuler le tout dans des classes. Cela donne un code relativement lisible, mais très verbeux (trop à mon goût) :

  1. class MainHandler(tornado.web.RequestHandler):
  2.     @tornado.web.asynchronous
  3.     def get(self):
  4.         http = tornado.httpclient.AsyncHTTPClient()
  5.         http.fetch("http://friendfeed-api.com/v2/feed/bret",
  6.                    callback=self.async_callback(self.on_response))
  7.  
  8.     def on_response(self, response):
  9.         if response.error: raise tornado.web.HTTPError(500)
  10.         json = tornado.escape.json_decode(response.body)
  11.         self.write("Fetched " + str(len(json["entries"])) + " entries "
  12.                    "from the FriendFeed API")
  13.         self.finish()

D'autres frameworks comme Node.js tâtonnent encore. Cela se voit par exemple sur ce long de fil de commentaires, qui a amené à la suppression des Promises dans node-v0.1.30. L'article "Do" it fast! est également une réflexion très intéressante sur le sujet.

Une des solutions possibles est de s'inspirer du modèle de passage de messages proposé par Erlang ou des chans et goroutines de Go :

  1. c := make(chan int)  // Allocate a channel.
  2. // Start the sort in a goroutine; when it completes, signal on the channel.
  3. go func() {
  4.     list.Sort()
  5.     c <- 1  // Send a signal; value does not matter. 
  6. }()
  7. doSomethingForAWhile()
  8. <-c   // Wait for sort to finish; discard sent value.

Pour conclure, je dirais que la seule chose qui est sûr aujourd'hui, c'est que les frameworks de demain seront asynchrones et proposeront des API adaptées à ce nouveau fonctionnement, mais pour le moment, nous n'en sommes encore réduit à explorer les différentes possibilités.

Some times ago, I was trying to re-synchronize subtitles and video whithin VLC player, the typical : "150 ms after ... erf, 50 ms before, that's it ! no ... it's still desynchronized ...".

After giving up, and by curiosity, I opened the file, and found that the SubRip format is, in fact, quite simple. Just for trying, I read and displayed them in a browser. This is the way SubRipReader was born, using Mootools 1.2.

Edit : SubRipReader has been deleted since then, its functionnalities are now integrated into the Mooplay project. More to come about it.

Unfortunately, it won't resolve my initial problem. But it's still able to load the subtitles through an Ajax request, to parse them, and to display them step by step, into a DOM element.

It supports :

  • play / pause states
  • overlapping subtitle levels : when several lines of texts should be displayed at the same time, each one took a different css class :
    1. 2
    2. 00:00:15,000 --> 00:00:25,000
    3. A text
    4.  
    5. 3
    6. 00:00:20,850 --> 00:00:30,000
    7. An other text

    For instance, the previous subtitles lines will be injected in DOM and could be displayed like that, depending of your custom styles :

    1. <div class="overlapping0"><p>A text</p></div>
    2. <div class="overlapping1"><p>An other text</p></div>

    subtitles screenshot

For playing subtitles, use Srt.Parser and Srt.Reader :

  1. var myReader = null;
  2.  
  3. var myParser = new Srt.Parser({
  4.     url: 'test.srt', // the subtitle file url
  5.     onComplete: function(data) {
  6.         myReader = new Srt.Reader(data, {
  7.             container: 'mysubtitlecontainer', // where to display subtitles
  8.             time_container: 'mytimecontainer' // where to optionnaly display the internal timer state
  9.         });
  10.     }
  11. });

Then, you should be able to call the start and pause actions :

  1. myReader.start();
  2. myReader.pause();

I've just seen about the Universal Subtitle Format, it's going to be the time to implement its own parser !

This must be PHP week on AF83's github, with a bunch of stuff that we hope can help someone out there. I will post about toupti and the others later, but first:

François (francois2metz) just released session-cookie that does just that. It puts your session data in the cookie.

Usually when you are doing PHP your session data is somewhere in a local store (either on the disk as a default, in your database or in some kind of DHT like memecache).

PHP lets you set quite easily the session handler for any of these methods (look at http://www.php.net/manual/en/function.session-set-save-handler.php).

Now this does pose some serious issues in terms of scalability. If you use a database or the file system this can be very hard on your disks. Opening a session is expensive, and distributing it over a large number of servers is hard complicated and may require more code then you imagined.

Sometimes all you want to have from the session is the user_id, yet when you get the session cookie you still have to pay a round trip to the database just to get it from there.

So.. one simple, cool solution, is just to send this data to the client as a cookie. Now you don't really want the user to be able to change the data (for example changing the user id and logging-in as a super duper admin). But this is not hard.. just encrypt it with your own secret, and the user will not be able either to modify or look at it.

Basic Usage
===========

<?php
// just include session class
require_once 'session.php';
require_once 
'Crypt/Blowfish.php'// pear package, only needed when using SessionInCookie_DefaultCipher

SessionInCookie::setCipher(new SessionInCookie_DefaultCipher('mysecretkey'));

// start session normally
start_session();

// Read and write in session
$_SESSION['foo'] = 'bar';

// juste before output, call session_write_close
session_write_close();

// WARNING: now session data have been send to the client via encrypted cookie. You *CANNOT* write on $_SESSION.

echo 'Hello Word';
?>

Advanced Usage
==============

<?php
Custom cipher
-------------

class MyCipher implements SessionInCookie_Cipher
{
    public function 
encrypt($data)
    {
        return 
$data;
    }

    public function decrypt($data)
    {
        return 
$data;
    }
}

SessionInCookie::setCipher(new MyCipher());

Debug
-----

You can use SessionInCookie_DummyCipher

SessionInCookie::setCipher(new SessionInCookie_DummyCipher());

?>

I do not think this solution is great when you have too much data in the session.. but that anyway is probably a very bad idea. If the data is important ... please remember : sessions die.

get the code at: http://github.com/AF83/session-cookie

Enjoy

(btw some frameworks, written by serious people take this approach as a default...)

Git

Git permet d'ignorer des fichiers par projet en les listant dans un fichier .gitignore. On peut également ignorer des motifs comme log/*.log pour ignorer tous les fichiers du répertoire log avec l'extension .log

Ce mécanisme est souvent utilisé pour ignorer les fichiers temporaires utilisés par les éditeurs de texte. On retrouve alors des motifs comme *~ (emacs) ou *.swp (vim).

Pourtant, ce n'est pas l'idéal. D'une part, ce fichier est partagé par tous les utilisateurs, alors que cet usage voudrait qu'il soit spécifique à chaque utilisateur. D'autre part, il faut recopier à chaque nouveau projet les mêmes motifs, alors qu'il serait tellement plus simple d'avoir à les lister au même endroit.

La solution existe : un fichier .gitconfig par utilisateur. En plus, c'est très simple à mettre en place.

Il faut déclarer à git quel fichier utiliser pour ignorer de manière globale, et mettre les motifs qui nous intéressent dans ce fichier :


git config --global core.excludesfile ~/.gitignore
echo "*~" >> ~/.gitignore
echo "*.swp" >> ~/.gitignore
echo ".DS_Store" >> ~/.gitignore
echo "*.tmproj" >> ~/.gitignore
echo "tmtags" >> ~/.gitignore

Voilà, ce n'était pas compliqué !

Cookies

Et voilà sur le même thème un petit billet en français pour les non anglophones.

Nous avons libéré sur github une petite classe qui installe un gestionnaire de session personnalisé. Sa particularité est de stocker les données de session dans un cookie chiffré.

L'intérêt est que nous n'avons plus besoin de partager les sessions entre serveurs si nous avons plusieurs frontaux.

Le chiffrement permet d'éviter toute modification par l'utilisateur du contenu du cookie. Le chiffrement par défaut utilise la paquet pear Crypt_Blowfish.

Son utilisation est très simple, on utilise toujours les fonctions habituelles de gestion de session (session_start(), session_destroy(), etc.). La seule exigence est de fermer la session en écriture juste avant la sortie HTML/whatever.

<?php
// just include session class
require_once 'session.php';
require_once 
'Crypt/Blowfish.php'// pear package, only needed when using SessionInCookie_DefaultCipher

SessionInCookie::setCipher(new SessionInCookie_DefaultCipher('mysecretkey'));

// start session normally
start_session();

// Read and write in session
$_SESSION['foo'] = 'bar';

// juste before output, call session_write_close
session_write_close();

// WARNING: now session data have been send to the client via encrypted cookie. You *CANNOT* write on $_SESSION.

echo 'Hello Word';
?>

Vous pouvez retrouver le code sur le dépot session-cookie. L'utilisation est décrite dans le README.

Rails 3

Fin 2008, les développeurs de Merb et Rails ont annoncé qu'ils allaient joindre leurs efforts pour sortir un Rails 3 qui combinerait les avantages de Rails et ceux de Merb. Depuis, nous avons porté beaucoup d'espoirs qui s'annonce très prometteuse, mais qui est également une source de frustration que cette version ne soit pas déjà disponible. Enfin, la semaine dernière, Rails 3 est officiellement sorti en beta (avec les release notes détaillées).

Pour le moment, Rails 3 reste encore le domaine des développeurs aventureux, ceux qui n'ont pas peur de rencontrer des bugs. Par exemple, de nombreux plugins ne sont pas encore compatibles avec Rails 3 (railsplugins.org est une bonne source pour connaître la liste de ceux qui le sont).

Si vous faites parti de cette catégorie, alors deux choix s'offrent à vous. Vous pouvez commencer une nouvelle application avec Rails 3. Le 200ème railscast est un très point de départ pour ça. L'autre choix, plus ambitieux, est de porter une application Rails existante vers Rails 3. Je vous conseille alors de vous faire aider par le plugin rails_upgrade en vous laissant guider par les explications du peepcode Rails 3 Upgrade.

Dans tous les cas, il est intéressant de commencer à se plonger dans les nouveautés de Rails 3. Les articles sur le sujet sont dispersés sur de nombreux blogs, mais des listes permettent de s'y retrouver. Celle de Ruby Inside me semble être la plus complète pour le moment.

Je vous encourage également à participer à l'initiative Give Back to Open Source. C'est très simple : prenez votre plus gros projet Rails, regardez les gems et plugins que vous utilisez, et pour chacun d'eux, contribuez en retour : donnez de l'argent, corrigez un bug, écrivez de la documentation, ou simplement, remerciez son auteur. L'important est de donner en retour.

Mise-à-jour : le Rails 3 Upgrade Handbook est un guide PDF qui vous guide dans votre migration vers Rails 3. Il coûte 12$, mais il les vaut. Vous y trouverez toutes les informations utiles, y compris certaines qui sont très difficiles à trouver ailleurs.

Madison Street cable car derailed in snow, 1929

Quand on développe une application web, la sécurité est toujours une problématique à prendre au sérieux. Négligez-la et vous pouvez être sûr qu'un jour ou l'autre, un petit malin se fera le plaisir de vous montrer vos failles. Une attaque simple mais très répandue est l'injection XSS (ou Cross-site scripting). Le principe est d'envoyer du code HTML dans un site web pour qu'il soit interprété par le navigateur d'un autre utilisateur. Par exemple, on peut poster la ligne suivante sur un forum pour essayer d'ennuyer les autres utilisateurs :

  1. <script>alert('All your base are belong to us!');</script>

Cette attaque n'est toutefois pas à prendre à la légère. Elle permet par exemple de voler les cookies d'un utilisateur et donc sa session, ou encore de lui faire des actions indésirables sur le site attaqué.

Il est donc nécessaire de se protéger de cette attaque et, rassurez-vous, Ruby on Rails a tout ce qu'il faut pour faire ça simplement. Jusqu'à maintenant, la méthode pour faire ça consiste à indiquer à Rails les chaînes de caractères qui contiennent du contenu manipulé par l'utilisateur et qui pourraient donc être dangereuses. Dans le cas le plus fréquent, une telle chaîne ne doit pas du tout contenir de balise HTML, et on peut alors utiliser le helper h qui transforme les caractères dangereux en entités HTML. Exemple :

  1. Hello <%=h @name %>

Dans le cas contraire où l'utilisateur a le droit d'entrer du texte HTMl enrichi avec des balises HTML, il convient de limiter les balises et attributs autorisés. Le helper sanitize permet de faire ça :

  1. Biographie : <%=sanitize @bio %>

Cela fonctionne très bien, mais il y a toujours le risque d'oublier un h ou un sanitize, ce qui laisserait une porte pour un attaquant. C'est pourquoi il existe des plugins Rails qui favorisent l'approche inverse, à savoir indiquer quelles sont les chaînes de caractères qui sont sûres et d'échapper tout le reste. C'est également l'approche que Rails 3 offrira lors de sa sortie en février 2010.

Dans Rails 3, les chaînes de caractères auront un attribut html_safe, qui indiquera si la chaîne est sûre. Par défaut, l'attribut vaudra false, et cette chaîne sera échappée lors du rendering.

Bien entendu, votre page HTML contient des balises et il est donc nécessaire de pouvoir construire ces balises sans qu'elles soient échappées. Tout d'abord, cette protection ne vise que des contenus venant de l'utilisateur, il est donc inutile d'échapper le contenu "en dur" des vues : seul ce qui se trouvent entre <%= et %> est concerné. Dans l'exemple suivant, le nom apparaîtra bien en gras :

  1. Hello <strong><%= @name %></strong>

Une autre source de contenus légitimes est la génération de code HTML par les helpers de Rails. Heureusement, ces helpers génèrent des chaînes de caractères marquées comme sûres après avoir échappé leurs entrées. Exemple :

  1. <%= tag(:p, "<script>alert('hello');</script>") %>  # => "<p>&lt;script&gt;alert('hello');&lt;script&gt;</p>"

Enfin, il vous reste la possibilité de marquer vous-mêmes les chaînes comme étant sûres. Pour cela, vous pouvez utilisez raw quand vous êtes dans une vue, ou appeler html_safe sur la chaîne si vous êtes dans un helper. Exemple :

  1. <%= raw @site_title %>
  1. def evil_js
  2.   "<script>alert('This site is evil. Mwahahah!');</script>".html_safe
  3. end

Si vous souhaitez en savoir plus, je vous conseille la lecture de SafeBuffers and Rails 3.0 de Yehuda Katz.

AF83 sort une gem que nous utilisons dans nos projets de sites communautaires. Has_media est une bibliothèque pour ActiveRecord, pour gérer les médias dans les modèles avec une simple déclaration :


class User < ActiveRecord::Base
has_one_medium :avatar, :only => :image
end

On ajoute avec cette ligne un "champ" image pour un utilisateur. Il ne reste plus qu'à ajouter un file_field :avatar dans votre formulaire pour que l'image soit associée à l'utilisateur. L'upload est géré par carrierwave. Les méthodes de classe has_one_medium ou has_many_media ajoutent automatiquement les getter et setter pour l'avatar d'un utilisateur.

Les validations sont faites automatiquement selon le type de média défini par l'option :only => :image.

Cas d'utilisation :

# Créer la migration
./script/generate has_media


# Ajouter la gem dans config/environment.rb
config.gem 'has_media'


# Changer les options de has_media dans config/initializers/has_media.rb
HasMedia.directory_path = "media" # Placer les médias dans Rails.root, 'public', 'media'
HasMedia.directory_uri = "/media"
HasMedia.errors_messages = {:type_error => I18n.t('has_media.errors.type_error')}


# Pour le modèle voir plus haut et sur le formulaire
<p>
<%= f.label :avatar %>
<%= f.file_field :avatar %>
</p>
</code>

HasMedia ne gère pas les thumbnails (AF83 utilise une autre application pour cela), mais c'est facilement faisable avec carrierwave.

Plus d'informations : http://github.com/AF83/has_media
Installation : gem install has_media