Skip to content

Resumable file upload #153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 13, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions 5-network/09-resume-upload/article.md
Original file line number Diff line number Diff line change
@@ -1,82 +1,82 @@
# Resumable file upload
# Upload pouvant être repris

With `fetch` method it's fairly easy to upload a file.
Avec la méthode `fetch`, il est assez facile de upload un fichier.

How to resume the upload after lost connection? There's no built-in option for that, but we have the pieces to implement it.
Comment reprendre l'upload après une perte de connexion ? Il n'y a pas d'option intégrée pour cela, mais nous avons les pièces pour l'implémenter.

Resumable uploads should come with upload progress indication, as we expect big files (if we may need to resume). So, as `fetch` doesn't allow to track upload progress, we'll use [XMLHttpRequest](info:xmlhttprequest).
Les uploads pouvant être repris devraient être accompagnés d'une indication de progression, puisque nous pouvons nous attendre à de gros fichiers (au cas où on devrait reprendre). Donc, comme `fetch` ne permet pas de suivre la progression du téléchargement, nous utiliserons [XMLHttpRequest](info:xmlhttprequest).

## Not-so-useful progress event
## Événement de progression pas si utile

To resume upload, we need to know how much was uploaded till the connection was lost.
Pour reprendre l'upload, nous devons savoir combien a été uploadé jusqu'à ce que la connexion soit perdue.

There's `xhr.upload.onprogress` to track upload progress.
Il y a `xhr.upload.onprogress` pour suivre la progression de l'upload.

Unfortunately, it won't help us to resume the upload here, as it triggers when the data is *sent*, but was it received by the server? The browser doesn't know.
Malheureusement, cela ne nous aidera pas à reprendre l'upload ici, car cela ne se déclenche que lorsque les données sont *envoyées*, mais est-ce que le serveur l'a reçu ? Le navigateur ne sait pas.

Maybe it was buffered by a local network proxy, or maybe the remote server process just died and couldn't process them, or it was just lost in the middle and didn't reach the receiver.
Peut-être que cela a été mis en mémoire tampon par un proxy de réseau local, ou peut-être que le processus du serveur distant vient de mourir et n'a pas pu les traiter, ou cela a juste été perdu au milieu et n'a pas atteint le destinataire.

That's why this event is only useful to show a nice progress bar.
C'est pourquoi cet événement n'est utile que pour afficher une belle barre de progression.

To resume upload, we need to know *exactly* the number of bytes received by the server. And only the server can tell that, so we'll make an additional request.
Pour reprendre l'upload, nous devons connaître *exactement* le nombre d'octets reçus par le serveur. Et seul le serveur peut le dire, nous ferons donc une demande supplémentaire.

## Algorithm
## Algorithme

1. First, create a file id, to uniquely identify the file we're going to upload:
1. Créer d'abord un identifiant de fichier pour identifier de manière unique le fichier que nous allons uploader :
```js
let fileId = file.name + '-' + file.size + '-' + +file.lastModifiedDate;
```
That's needed for resume upload, to tell the server what we're resuming.
Cela est nécessaire pour reprendre l'upload, pour indiquer au serveur ce que nous reprenons.

If the name or the size or the last modification date changes, then there'll be another `fileId`.
Si le nom ou la taille ou la dernière date de modification change, alors il y aura un autre `fileId`.

2. Send a request to the server, asking how many bytes it already has, like this:
2. Envoyer une demande au serveur, lui demandant combien d'octets il possède déjà, comme ceci :
```js
let response = await fetch('status', {
headers: {
'X-File-Id': fileId
}
});

// The server has that many bytes
// Le serveur a autant d'octets
let startByte = +await response.text();
```

This assumes that the server tracks file uploads by `X-File-Id` header. Should be implemented at server-side.
Cela suppose que le serveur effectue le suivi des uploads de fichiers par l'en-tête `X-File-Id`. Doit être implémenté côté serveur.

If the file don't yet exist at the server, then the server response should be `0`
Si le fichier n'existe pas encore sur le serveur, la réponse du serveur doit être `0`.

3. Then, we can use `Blob` method `slice` to send the file from `startByte`:
3. Ensuite, nous pouvons utiliser un `Blob` par la méhtode `slice` pour envoyer le fichier depuis `startByte` :
```js
xhr.open("POST", "upload", true);

// File id, so that the server knows which file we upload
// Identifiant du fichier, afin que le serveur sache quel fichier nous uploadons
xhr.setRequestHeader('X-File-Id', fileId);

// The byte we're resuming from, so the server knows we're resuming
// L'octet à partir duquel nous reprenons, donc le serveur sait que nous reprenons
xhr.setRequestHeader('X-Start-Byte', startByte);

xhr.upload.onprogress = (e) => {
console.log(`Uploaded ${startByte + e.loaded} of ${startByte + e.total}`);
};

// file can be from input.files[0] or another source
// le fichier peut provenir de input.files[0] ou d'une autre source
xhr.send(file.slice(startByte));
```

Here we send the server both file id as `X-File-Id`, so it knows which file we're uploading, and the starting byte as `X-Start-Byte`, so it knows we're not uploading it initially, but resuming.
Ici, nous envoyons au serveur à la fois l'ID du fichier en tant que `X-File-Id`, afin qu'il sache quel fichier nous uploadons, et l'octet de départ en tant que `X-Start-Byte`, afin qu'il sache que nous ne l'uploadons pas de zéro, mais en reprenant.

The server should check its records, and if there was an upload of that file, and the current uploaded size is exactly `X-Start-Byte`, then append the data to it.
Le serveur doit vérifier ses enregistrements et s'il y a eu un upload de ce fichier et que la taille actuellement téléchargée est exactement `X-Start-Byte`, alors il y ajoute les données.


Here's the demo with both client and server code, written on Node.js.
Voici la démo avec le code client et serveur, écrite sur Node.js.

It works only partially on this site, as Node.js is behind another server named Nginx, that buffers uploads, passing them to Node.js when fully complete.
Cela ne fonctionne que partiellement sur ce site, car Node.js est derrière un autre serveur nommé Nginx, qui met en mémoire tampon les uploads, en les transmettant à Node.js que lorsqu'il est complètement terminé.

But you can download it and run locally for the full demonstration:
Mais vous pouvez le télécharger et l'exécuter localement pour la démonstration complète :

[codetabs src="upload-resume" height=200]

As we can see, modern networking methods are close to file managers in their capabilities -- control over headers, progress indicator, sending file parts, etc.
Comme nous pouvons le voir, les méthodes de mise en réseau modernes sont proches des gestionnaires de fichiers dans leurs capacités - contrôle des en-têtes, indicateur de progression, envoi de parties de fichier, etc...

We can implement resumable upload and much more.
Nous pouvons implémenter un upload pouvant être repris et bien plus encore.