Blog tech

Dragonfly et Fineuploader

Rédigé par Nicolas Zermati | 4 décembre 2012

Aujourd’hui, je voudrais partager une astuce pour faire fonctionner Dragonfly et Fineuploader ensemble.

Fineuploader permet de fournir une expérience d’envoi de fichier intuitive et visuelle pour l’utilisateur. Dragonfly permet, grâce à un middleware Rack, de rendre intuitive la manipulation des fichiers pour le developpeur.

Récemment, pour envoyer des fichiers de manière asynchrone, j’ai utilisé ces deux outils. Et, ô surprise, cela ne s’est pas passé comme prévu.

Dragonfly seul

Dragonfly s’utilise très bien sans fineuploader. Prenons le cas où nous avons un modèle simplifié de pièce jointe suivant : Attachment. Les colonnes attendues pour la table attachments sont : id, file_uid et file_name.

# app/models/attachment.rb
class Attachment < ActiveRecord::Base
  file_accessor :file
end

Voilà maintenant deux extraits du controlleur et de la vue associée à la création.

<%# app/views/attachments/new.html.erb %>
<% form_for @attachment, :html => {:multipart => true} do |f| %>
  <%= f.file_field :file %>
  <%= f.submit t('.submit') %>
<% end %>
# app/controllers/attachments_controller.rb
def new
  @attachment = Attachment.new
end

def create
  @attachment = Attachment.new(params[:attachment])
  @attachment.save ? redirect_to(action: index, notice: I18n.t('...')) : render(:new)
end

Comme on peut le voir, l’action de création est très simple. Dragonfly s’occupera de remplir les colonnes file_uid et file_name de la base de donnée ainsi que de stocker le fichier présent dans la requête.

Bien sûr, il faut configurer Dragonfly pour définir, entre autre, les répertoires de sauvegarde des fichiers ainsi que du cache.

Fineuploader seul

Fineuploader est un outil écrit en Javascript. Il n’a pas de dépendance vers d’autres bibliothèques telle que JQuery. L’outil est complet et offre un bon niveau de personnalisation en terme de style comme de comportement.

Me concernant, j’utilise plutôt FineUploaderBasic qui laisse le style ainsi que le comportement entièrement à la charge du programmeur.

Fineuploader est aussi cross-navigateur puisqu’il dispose de deux back-ends : un premier basé sur FormData et un second basé sur les iframes pour les navigateurs ne supportant pas le premier. Dans les deux cas (FormData ou iframe), Fineuploader va passer les paramètres de la requête HTTP dans l’URL et le fichier à envoyer en binaire dans le corps.

Coté serveur on se retrouve donc avec le nom du fichier envoyé dans paramètres dit GET : ?qqfile=mon-image.png et le contenu du fichier directement dans le corps du message.

Si l’on utilise Internet Explorer (ou tout autre navigateur ne supportant pas FormData), c’est encore différent puisqu’on a le nom du fichier dans la query string (toujours en GET) et le contenu du fichier dans le corps mais cette fois au format Multipart.

Dragonfly + Fineuploader

Pour adapter notre action de création à Fineuploader (utilisant FormData) on trouvera ce genre de code :

path = File.join(TEMPORARY_FILE_PATH, params[:qqfile])
file = File.new(path, 'wb')
file.write(request.body.read)
file.close

attachment = Attachment.new(file: file)

La pratique ci-dessus n’est pas à recommander. Dragonfly exige que ses paramètres soient passés dans le corps de la requête en respectant le format multipart. Mieux vaut utiliser l’option request.forceMultipart de Fineuploader qui permettra, même en utilisant FormData, d’encoder le corps de la requête au format multipart.

Même si les paramètres issus de la query string et du corps sont fusionnés dans la variable params dans Rails, j’ai constaté que le middleware de Dragonfly ne fonctionait que si tous ses paramètres se trouvaient dans le corps de la requête. Or, on a vu que Fineuploader séparait les paramètres entre la query string et le corps.

Patch

Le patch que je vous propose attaque Fineuploader plutôt que Dragonfly pour différentes raisons. Dans un premier temps, Fineuploader est beaucoup plus simple que Dragonfly (en termes d’architecture et de nombre de lignes). Ensuite, cela me semble plus cohérent de passer tous les paramètres en utilisant le même type de communication.

Je suis bien conscient que Fineuploader utilise sans doute ces mécanismes pour optimiser la taille de ses requêtes et que je vais à l’encontre de ce principe.

Voici donc le fameux patch (gist et raw) qui va grouper tous les paramètres de Fineuploader dans le corps d’une requête au format multipart (tant FormData qu’iframe) :

Pensez simplement à utiliser l’option request.forceMultipart et vous pourrez utiliser Dragonfly de la manière la plus transparente qui soit, quelque soit le navigateur.

Conclusion

J’espère que cette astuce vous aura été utile et vous aidera à mieux comprendre le fonctionnement de Fineuploader. N’hésitez pas à partager vos retours d’expériences sur l’envoi asynchrone de fichiers avec Rails : quels outils utilisez-vous, avez-vous rencontré des difficultés ?

L’équipe Synbioz.

Libres d’être ensemble.