Présentation du service
J'ouvre aujourd'hui un nouveau service sur mon domaine qui permet de faire de l'échange de fichiers assez lourds (plusieurs centaines de MB possible).
Le besoin initial qui a motivé la programmation de shareit (done by me) est assez simple:
- une amie voulait m'envoyer une vidéo d'une de mes prestations aux bolas
- un parent voulait envoyer des photos d'une sortie à une autre personne
La plupart du temps avec ces personnes, les échanges se font par mail. Le problème c'est que dans le premier cas la vidéo était trop grosse pour être acceptée par les serveurs mails, et dans le second cas, envoyer 20 mails pour faire photo par photo car l'ensemble est trop gros, ce n'est absolument pas pratique.
L'idée du service shareit c'est de déposer un fichier sur une page web. Cette page web génère ensuite un lien qu'il suffit de partager par mail, par sms, ou encore par messagerie instantanée. Afin que le serveur ne se remplisse pas de fichiers qui ne sont là que le temps d'un échange entre deux personnes (ou potentiellement un peu plus), le lien est valide 10 jours. Ensuite le fichier est automatiquement nettoyer. Faire du stockage de fichier n'est pas le but du service.
Je parle ici de sms et différents moyens car dû à la nature webomatique (mot inexistant <3) du service, il est accessible sur téléphone, sur tablette, ordinateur fixe ou portable, et tout simplement toute plateforme qui sait interpréter du html (version 5, mais un parseur version 4 fonctionnera).
Pour éviter des abus, des fois que mon domaine se retrouve un jour malencontreusement sur les moteurs de recherches, j'ai en pratique, mis une protection par mot de passe (et redirection sur version https) pour l'upload. Le download n'est pas protégé car le temps de validité n'est pas assez long pour que ça me dérange, et ça serait pas pratique pour une plateforme de partage.
Éléments techniques
Concrètement le service est programmé en python via le micro-framework bottle.
Gestion timeout
Le délai de 10 jours est simplement géré avec un find. Tout fichier ayant une date de modification (qui est souvent égale à la date de création sur le service) qui dépasse 10 jours est supprimé. J'ai mis ça dans un cron et hop, ça roule.
#!/bin/sh find /files/ -type f -mtime +10 ! -name '*.meta' \ -exec rm {} \; -exec rm {}.meta \;
Là, c'est gratuit, c'est l'intégralité du code qui gère l'expiration des fichiers. C'est pas le plus intéressant sur la plateforme, mais ça montre mon désire de faire les choses simplement et vite. One command
D'ailleurs, en terme de temps, la programmation de la bête: 1h.
Compatibilité
J'ai utilisé les templates jinja2 car les templates bottle me provoquaient des erreurs. Il semblerait que c'était juste une question de version de bottle mais bon, j'ai fait la transition et je trouve les templates jinja2 beaucoup plus simples à utiliser, plus logiques.
Cependant, je dois noter un problème à cause de ce petit ensemble. D'habitude j'utilise une image de Debian wheezy pour déployer mes hosts. Mais wheezy a un problème de compatibilité entre jinja2 et l'interpréteur python disponible (version 3.2). J'ai donc tenté de passer à la Debian testing (jessie) mais là un autre problème... J'utilise uwsgi comme serveur wsgi pour héberger l'application, et uwsgi n'est pas disponible sur jessie dû à une transition... Elle n'est disponible que sur la version non stable sid. J'ai donc créé en urgence (10 min) une image pour Ubuntu trusty où il n'y a pas ces problèmes là.
Implémentation
Les fichiers sont enregistrés sous un nom de fichier qui correspond directement à un hash. Cela m'épargne la gestion des noms de fichiers avec les caractères qui passeraient pas ou encore les fichiers qui portent le même noms, les fameux "Sans nom.jpg". Pour que les téléchargements se retrouvent quand même avec un nom de fichier utile, le nom de fichier d'origine est enregistré dans un fichier .meta qui est juste une sérialisation en json d'un dictionnaire.
Je dois noter un problème avec la détection de la langue. Pour éviter de compliquer le code, j'ai essayé de faire les choses simple en lisant uniquement le début du header Accept-Language en espérant que le langage préféré vient le premier.
valid_languages = ['en', 'fr'] def get_language(): headlang = \ bottle.request.headers.get('accept-language', 'en')[:2].lower() if headlang in valid_languages: return headlang return valid_languages[0]
Mais j'ai pu remarqué que ce n'est pas toujours le cas... Pour suivre le standard du header, il faudrait le décomposer, associer les poids à chaque langue, trier en un tableau par poids... Bref, si j'ai l'envie j'améliorerai cette partie là, mais pour l'instant, le site web est tellement simple qu'il semblerait que même les anglophobes arrivent à l'utiliser en anglais, bon signe.