|
1 | | -# Resumable file upload |
| 1 | +# Возобновляемая загрузка файлов |
2 | 2 |
|
3 | | -With `fetch` method it's fairly easy to upload a file. |
| 3 | +С помощью метода `fetch` можно достаточно просто загрузить файл на сервер. |
4 | 4 |
|
5 | | -How to resume the upload after lost connection? There's no built-in option for that, but we have the pieces to implement it. |
| 5 | +Но как возобновить загрузку, если соединение прервалось? Для этого не предусмотрено какого-то встроенного способа, но у нас есть все средства, чтобы решить эту задачу самостоятельно. |
6 | 6 |
|
7 | | -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). |
| 7 | +Возобновляемая загрузка должна сопровождаться индикацией прогресса, так как мы ожидаем, что могут загружаться большие по весу файлы. Поскольку `fetch` не позволяет отслеживать прогресс загрузки, то мы будем использовать [XMLHttpRequest](info:xmlhttprequest). |
8 | 8 |
|
9 | | -## Not-so-useful progress event |
| 9 | +## Не очень полезное событие progress |
10 | 10 |
|
11 | | -To resume upload, we need to know how much was uploaded till the connection was lost. |
| 11 | +Чтобы возобновить загрузку, нам нужно знать, какая часть файла была успешно передана до того, как соединение прервалось. |
12 | 12 |
|
13 | | -There's `xhr.upload.onprogress` to track upload progress. |
| 13 | +Можно установить обработчик `xhr.upload.onprogress`, чтобы отслеживать процесс загрузки. |
14 | 14 |
|
15 | | -Unfortunately, it's useless here, as it triggers when the data is *sent*, but was it received by the server? The browser doesn't know. |
| 15 | +К сожалению, это бесполезно, так как этот обработчик вызывается, только когда данные *отправляются*, но были ли они получены сервером? Браузер этого не знает. |
16 | 16 |
|
17 | | -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 when the connection broke, and didn't reach the receiver. |
| 17 | +Возможно, отправленные данные оказались в буфере прокси-сервера локальной сети или удалённый сервер просто отключился и не смог принять их, или данные потерялись где-то по пути при разрыве соединения и так и не достигли пункта назначения. |
18 | 18 |
|
19 | | -So, this event is only useful to show a nice progress bar. |
| 19 | +Таким образом, событие `progress` подходит только для того, чтобы показывать красивый индикатор загрузки, не более. |
20 | 20 |
|
21 | | -To resume upload, we need to know exactly the number of bytes received by the server. And only the server can tell that. |
| 21 | +Для возобновления же загрузки нужно точно знать, сколько байт было получено сервером. И только сам сервер может поделиться с нами этой информацией. |
22 | 22 |
|
23 | | -## Algorithm |
| 23 | +## Алгоритм |
24 | 24 |
|
25 | | -1. First, we create a file id, to uniquely identify the file we're uploading, e.g. |
| 25 | +1. Во-первых, давайте присвоим загружаемому файлу уникальный идентификатор |
26 | 26 | ```js |
27 | 27 | let fileId = file.name + '-' + file.size + '-' + +file.lastModifiedDate; |
28 | 28 | ``` |
29 | | - That's needed for resume upload, to tell the server what we're resuming. |
| 29 | + Это нужно, чтобы при возобновлении загрузки серверу было понятно, какой файл мы продолжаем загружать. |
30 | 30 |
|
31 | | -2. Send a request to the server, asking how many bytes it already has, like this: |
| 31 | +2. Далее, посылаем запрос к серверу с просьбой указать количество уже полученных байтов: |
32 | 32 | ```js |
33 | 33 | let response = await fetch('status', { |
34 | 34 | headers: { |
35 | 35 | 'X-File-Id': fileId |
36 | 36 | } |
37 | 37 | }); |
38 | 38 |
|
39 | | - // The server has that many bytes |
| 39 | + // сервер получил столько-то байтов |
40 | 40 | let startByte = +await response.text(); |
41 | 41 | ``` |
42 | 42 |
|
43 | | - This assumes that the server tracks file uploads by `X-File-Id` header. Should be implemented at server-side. |
| 43 | + Предполагается, что сервер учитывает загружаемые файлы с помощью заранее настроенного заголовка `X-File-Id`. |
44 | 44 |
|
45 | | -3. Then, we can use `Blob` method `slice` to send the file from `startByte`: |
| 45 | +3. Затем мы можем использовать метод `slice` объекта `Blob`, чтобы отправить данные, начиная со `startByte` байта: |
46 | 46 | ```js |
47 | 47 | xhr.open("POST", "upload", true); |
48 | 48 |
|
49 | | - // send file id, so that the server knows which file to resume |
| 49 | + // отправка идентификатора файла, чтобы сервер знал, загрузку чего мы потом возобновим |
50 | 50 | xhr.setRequestHeader('X-File-Id', fileId); |
51 | | - // send the byte we're resuming from, so the server knows we're resuming |
| 51 | + // отправка номера байта, начиная с которого мы будем отправлять данные. Таким образом, сервер поймёт, что мы возобновляем загрузку |
52 | 52 | xhr.setRequestHeader('X-Start-Byte', startByte); |
53 | 53 |
|
54 | 54 | xhr.upload.onprogress = (e) => { |
55 | 55 | console.log(`Uploaded ${startByte + e.loaded} of ${startByte + e.total}`); |
56 | 56 | }; |
57 | 57 |
|
58 | | - // file can be from input.files[0] or another source |
| 58 | + // файл может быть взят из input.files[0] или другого источника |
59 | 59 | xhr.send(file.slice(startByte)); |
60 | 60 | ``` |
61 | 61 |
|
62 | | - 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. |
| 62 | + Здесь мы посылаем серверу и идентификатор файла в заголовке `X-File-Id`, чтобы он знал, что мы загружаем, и номер стартового байта в заголовке `X-Start-Byte`, чтобы он понял, что мы продолжаем закачку, а не начинаем её с нуля. |
63 | 63 |
|
64 | | - 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. |
| 64 | + Сервер должен проверить информацию на своей стороне, и если обнаружится, что такой файл уже когда-то загружался, и его текущий размер равен значению из заголовка `X-Start-Byte`, то вновь принимаемые данные добавлять в этот файл. |
65 | 65 |
|
66 | 66 |
|
67 | | -Here's the demo with both client and server code, written on Node.js. |
| 67 | +Ниже представлен демо-код как для сервера (Node.js), так и для клиента. |
68 | 68 |
|
69 | | -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. |
| 69 | +Пример работает только частично на этом сайте, так как Node.js здесь располагается за другим веб-сервером Nginx, который сохраняет в своём буфере все загружаемые файлы и передаёт их дальше в Node.js только после завершения загрузки. |
70 | 70 |
|
71 | | -But you can download it and run locally for the full demonstration: |
| 71 | +Но вы можете скачать код и запустить его локально, чтобы увидеть полный пример в действии: |
72 | 72 |
|
73 | 73 | [codetabs src="upload-resume" height=200] |
74 | 74 |
|
75 | | -As you can see, modern networking methods are close to file managers in their capabilities -- control over headers, progress indicator, sending file parts, etc. |
| 75 | +Как вы видите, современные методы работы с сетью очень близки по своим возможностям к файловым менеджерам -- контроль заголовков, индикация прогресса загрузки, отправка данных по частям и так далее. |
0 commit comments