Le besoin initial
Le besoin d'ajouter des fichiers dans Kinto avait été identifié depuis le début du projet. Mais comme nous sommes une toute petite équipe, nous sommes un peu forcés d'attendre que des besoins se présentent en interne (chez Mozilla) pour se lancer dans l'implémentation.
Après les permissions, le chiffrement, les signatures et les notifications, nous avons enfin eu une demande pour les fichiers !
En effet, dans Fennec — la version mobile de Firefox — nous souhaitons livrer et mettre à jour des fichiers statiques (polices de caractères, icônes, dictionnaires de césures...) sans avoir à repackager et redéployer une nouvelle version de l'application.
Pour cela, un Kinto accessible publiquement en lecture-seule sera consulté par les mobiles pour synchroniser localement des collections d'enregistrements, qui eux fourniront les URLs vers les fichiers (sur un CDN). Les collections fonts, assets ou hyphenation seront mises à jour dès qu'un fichier sera ajouté ou remplacé sur un de leurs enregistrements.
Nous reviendrons avec plus de détails dans un autre article, mais cela ressemble beaucoup à ce que nous sommes en train de faire pour les révocations de certificats ou les listes noires d'addons dans Firefox.
La solution
Comme nous avons besoin de cette fonctionnalité assez rapidement et que ce cas d'utilisation n'est probablement pas très universel, nous avons décidé d'implémenter une solution sous forme de plugin Kinto [1].
Le support des fichiers sera donc optionnel dans un premier temps.
Fonctionnalités principales
- stockage des fichiers en local sur le serveur ou sur Amazon S3 ;
- le serveur calcule les métadonnées à la reception (hash, taille, ...) ;
- l'enregistrement lié est créé si besoin lors de l'envoi du fichier ;
- les permissions de l'enregistrement sont vérifiées au moment de l'envoi ;
- des attributs et permissions de l'enregistrement lié peuvent être spécifiés lors de l'envoi ;
- les fichiers sont de simples blobs opaques, qui peuvent être le résultat de compression ou chiffrement ;
- les fichiers sont supprimés lorsque les enregistrements sont supprimés.
Limitations
- un seul fichier par enregistrement ;
- les metadonnées du fichier joint sont stockées dans un attribut attachment parmis les autres attributs métier (plus de détails);
- dans un premier temps la récupération du fichier ne passera pas par Kinto. Pour les fichiers «privés», il faut donc que les URLs générées soient «secrètes».
Pour plus tard (ou bientôt selon les besoins)
- Support de l'envoi par chunk pour les gros fichiers
- Ajout d'un endpoint (optionnel) pour servir des fichiers complètement privés
[1] | ça nous permet aussi de remonter des soucis dans notre système de plugins :) |
HTTP API
Quand le plugin est activé, Kinto dispose d'un nouveau endpoint : un suffixe /attachment à l'URL de l'enregistrement.
Le fichier est posté avec le Content-Type multipart. Avec HTTPie, ça donne :
$ http --auth alice:passwd --verbose --form POST \
http://localhost:8888/v1/buckets/website/collections/assets/records/c2ce1975-0e52-4b2f-a5db-80166aeca689/attachment \
data='{"type": "wallpaper", "theme": "orange"}' \
attachment=@~/Pictures/background.jpg
HTTP/1.1 201 Created
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 209
Content-Type: application/json; charset=UTF-8
Date: Wed, 18 Nov 2015 08:22:18 GMT
Etag: "1447834938251"
Last-Modified: Wed, 18 Nov 2015 08:22:18 GMT
Location: http://localhost:8888/v1/buckets/website/collections/assets/c2ce1975-0e52-4b2f-a5db-80166aeca689
Server: waitress
{
"filename": "background.jpg",
"hash": "db511d372e98725a61278e90259c7d4c5484fc7a781d7dcc0c93d53b8929e2ba",
"location": "/files/MDcxJDAiBgNVBAMTG1JDUyBDZXJ0aWZpY2.jpg",
"mimetype": "image/jpeg",
"size": 1481798
}
L'enregistrement lié a été créé, et contient un attribut attachment avec les metadonnées du fichier, ainsi que les attributs supplémentaires spécifiés lors de l'envoi :
$ http --auth alice: GET \
http://localhost:8888/v1/buckets/website/collections/assets/records/c2ce1975-0e52-4b2f-a5db-80166aeca689
HTTP/1.1 200 OK
Access-Control-Expose-Headers: Content-Length, Expires, Alert, Retry-After, Last-Modified, ETag, Pragma, Cache-Control, Backoff
Cache-Control: no-cache
Content-Length: 360
Content-Type: application/json; charset=UTF-8
Date: Wed, 18 Nov 2015 08:24:15 GMT
Etag: "1447834938251"
Last-Modified: Wed, 18 Nov 2015 08:22:18 GMT
Server: waitress
{
"data": {
"id": "c2ce1975-0e52-4b2f-a5db-80166aeca688",
"last_modified": 1447834938251,
"theme": "orange",
"type": "wallpaper",
"attachment": {
"filename": "background.jpg",
"hash": "db511d372e98725a61278e90259c7d4c5484fc7a781d7dcc0c93d53b8929e2ba",
"location": "/files/MDcxJDAiBgNVBAMTG1JDUyBDZXJ0aWZpY2.jpg",
"mimetype": "image/jpeg",
"size": 1481798
}
},
"permissions": {
"write": ["basicauth:6de355038fd943a2dc91405063b91018bb5dd97a08d1beb95713d23c2909748f"]
}
}
Il est également possible de supprimer un attachment en effectuant une requête DELETE sur le /attachment de l'enregistrement.
Si l'attribut attachment est supprimé de l'enregistrement, le lien avec le fichier est tout de même conservé en interne, notamment pour s'assurer de sa suppression lors de la suppression de l'enregistrement.
Fichiers multiples
Il est possible de simuler l'ajout de plusieurs fichiers par enregistrements en utilisant une collection séparée, avec un attribut record_id par exemple : GET /buckets/kept/collections/attachments/records?record_id=<id>.
En revanche l'intégrité lors de la suppression de l'enregistrement lié devra être assurée manuellement (DELETE /.../attachments/records?record_id=<id>).
Démo
Si vous avez du feedback sur ces premiers pas, n'hésitez pas à nous en faire part !
Une première implémentation a été commencée, en utilisant Pyramid Storage.
Nous avons déjà eu l'occasion de tester l'API en soirée et ce fût un véritable succès!
Dans ce petit projet, un bot Telegram reçoit des messages et des contenus multimedia, et les transmet à un serveur Kinto sous forme de fichiers joints.
Une page Web synchronize et affiche les images, videos et sons en direct sur l'écran géant! Pendant la soirée, tout le monde a adoré envoyer un petit quelquechose, et il a suffi de faire une archive du répertoire de kinto-attachment pour distribuer un paquet de chouettes souvenirs!