1# Improving File Upload and Download Performance
2
3## Overview
4
5File transfer performance is critical to efficient data exchange between the client and server. An application with poor data exchange performance prolongs the loading process and may even cause screen freezing, delivering a poor user experience. On the contrary, efficient data exchange makes an application smoother.
6
7This topic describes two key technologies for upload/download and network requests: data compression and resumable download. Both technologies accelerate upload and download speed, reduce bandwidth usage, and boost data transmission efficiency.
8
9## Upload and Download APIs
10
11You can use the APIs provided by the [@ohos.net.http](../reference/apis-network-kit/js-apis-http.md) and [@ohos.request](../reference/apis-basic-services-kit/js-apis-request.md) modules to implement file upload and download. The [@ohos.net.http](../reference/apis-network-kit/js-apis-http.md) module provides the basic HTTP data request capability, which is not described in this topic. The [@ohos.request](../reference/apis-basic-services-kit/js-apis-request.md) module provides applications with basic upload, download, and background transmission agent capabilities. This module has the default concurrency capability of the task management system. It simplifies the implementation and management of downloads, and improves the security of data transmission. It also integrates the notification mechanism, and supports task status and progress query. It features flexibility, efficiency, scalability, reliability, consistency, and security.
12
13Specifically, the [@ohos.request (Upload and Download)](../reference/apis-basic-services-kit/js-apis-request.md) module provides the following functionalities:
14
15- Task management: The operations include creating, pausing, resuming, and deleting tasks, uploading and downloading files, and sending system notifications. The created tasks are classified into foreground tasks and background tasks. Foreground tasks are synchronous, can be displayed on modal screens, and follow the application lifecycle. Generally, a foreground task involves a small volume of data and can be completed within a short period of time. Examples are posting a WeChat Moment. Foreground tasks usually have a high priority and require more bandwidth resources. Background tasks are asynchronous, and can be displayed on any screen. Generally, a foreground task carries relatively large data and is time consuming. Examples are caching a movie and synchronizing hundreds of megabytes of data or even several GB of data. Background tasks have a low priority and are irrelevant to the application lifecycle.
16
17- Task query management: The system can query all tasks or information about the specified task, filter upload or download tasks, tasks within a time segment, foreground tasks, or background tasks, and clear the specified task. An application can query information about the specified task and specified hidden task.
18
19- Automatic task resumption: A task is automatically started or resumed when the network conditions are met. (The HTTP server must support resumable download.)
20
21- Security and privacy protection: The operations include network permission check, encrypted storage of task information, system API check, query and hiding of sensitive task fields (by both system APIs and public APIs), identification of traversal attacks, DoS attacks, zombie tasks, and malicious silent background tasks, and system-managed API permission. In addition, public APIs are allowed only to operate tasks created by themselves.
22
23- Log: The debug mode and release mode are provided. In debug mode, all logs about memory modification, disk read/write, network read/write, and logic branches can be printed. In release mode, only logs related to task failures and service exceptions are printed.
24
25- Retry upon failure: In case of unrecoverable failures, no retry is provided. In case of recoverable failures, such as network disconnection and network type mismatch, the task enters the queue and waits for network recovery. If the failure cause is network timeout, the task is retried once, and if network timeout is reported again, the task fails.
26
27- On-demand service and stop: Upload and download do not automatically start with the system. When your application proactively calls any APIs, upload and download automatically start. The network connection event triggers the start of upload and download. If no task is being processed or a task is waiting for network recovery in the queue, the system checks the queue 10 seconds later. If no task is being processed, the system instructs SAMGR to stop and uninstall the upload and download service. During service exit, new API requests may fail. In this case, check the service status on the client and retry to start the service as required.
28
29- Notification: Progress notifications are sent from the start to the end of a task. The notifications are triggered at a fixed interval, which is 1 second for a foreground task and 3 seconds for a background task. A progress notification is required for each change of the task status. A dedicated progress notification is triggered when the task is complete or fails. A suppression switch is also provided. You can turn on the switch during task creation to prevent frequent notifications.
30
31
32### State Transition of a Download Task
33
34When you use [@ohos.request](../reference/apis-basic-services-kit/js-apis-request.md) to execute a download task, the task has the following states: initial, ready, suspended, waiting, successful, and failed. You can call **create()** **start()**, **pause()**, **resume()**, **remove()**, and **stop()** to operate the task. The task result includes final-failed, final-completed, and recoverable-failed. You can also query the task state.
35
36**Figure 1** Module flowchart
37
38![Module flowchart](./figures/file-upload-and-download-pic1.png)
39
40## Typical Scenarios and Solutions
41
42**Scenario 1: Uploading Trivial Files on a Low-Bandwidth Network**
43
44In an environment with poor network connection and low bandwidth, establishing an HTTP connection may be time-consuming. In this case, you can use [data compression](#data-compression) to speed up page loading and reduce the number of HTTP requests and data traffic.
45
46**Scenario 2: Processing a Large Number of Resources**
47
48App store and web disk applications usually involve the transfer of a large volume of files. After the application resumes from a pause or network disconnection, restart the upload or download from the beginning may take a lot of time. To address this scenario, you can use [resumable transfer](#resumable-transfer).
49
50### Data Compression
51
52Data compression enables the system to compress data in your application, thereby reducing storage space and data transmission volume, saving bandwidth, and improving loading speed. It plays an important role in network transmission and storage, especially in scenarios of frequent data transmission or processing of a large amount of data.
53
54In application development, common data compression technologies are classified into the following types:
55
56- Lossy compression: applies only to images, videos, and audio files. It decreases the file size by reducing the image/video resolution or the audio quality to shorten the loading time and lower the bandwidth consumption.
57- Lossless compression: You can use [@ohos.zlib (Zip)](../reference/apis-basic-services-kit/js-apis-zlib.md) to pack and compress fragmented files, thereby reducing the number of upload requests. You can use cache for large files. Specifically, the server caches the MD5 value of the large file that has been uploaded, and the client, before uploading a large file, pre-generates an MD5 value and transfers it to the server for comparison. If the MD5 values are the same, the file exists on the server, and the file does not need to be uploaded again.
58
59
60The following uses uploading images in batches from Gallery as an example to describe the technology related to lossless compression.
61
62**Figure 2** Uploading images from Gallery
63
64![Uploading images](./figures/file-upload-and-download-pic2.png)
65
66The table below lists the test results of batch image uploading on the RK device. (For each image, the resolution is 480 x 640, the bit depth is 24, and the average size is 50 to 120 KB).
67
68| Image Count| Time Required Before Optimization (ms)| Time Required After Optimization (ms)|
69| --- | --- | ---|
70| 10 | 470 | 526 |
71| 20 | 1124 | 1091 |
72| ... | ... | ... |
73| 50 | 2379 | 2138 |
74| 80 | 3950 | 3258 |
75| ... | ... | ... |
76| 100 | 5276 | 3909 |
77
78**Figure 3** Comparison between the image count and upload duration
79
80![Comparison between the image count and upload duration](./figures/file-upload-and-download-pic3.png)
81
82The time required for uploading varies greatly according to the network status. In this example, the minimum value of multiple measurement results is used. According to the data, before the optimization, the time required increases linearly as the number of images increase. After the optimization, the time difference is not obvious when the number of images is small, as compression consumes extra time. However, as the number of images increases, the time difference becomes obvious.
83
84To implement data compression, proceed as follows:
85
861. Import the modules.
87
88   ```ts
89   import common from '@ohos.app.ability.common';
90   import fs from '@ohos.file.fs';
91   import zlib from '@ohos.zlib';
92   ```
93
942. Create a class related to compression and upload.
95
96   ```ts
97   class ZipUpload {
98     // Storage URI before the task is created.
99     private waitList: Array<string> = [];
100     // URIs of the images to upload.
101     private fileUris: Array<string> = [];
102     ...
103   }
104   ```
105
1063. Create a temporary folder to receive images from Gallery, compress the temporary folder, and add it to the list to be uploaded.
107
108   ```ts
109   // Compress the images.
110   async zipUploadFiles(fileUris: Array<string>): Promise<void> {
111     this.context = getContext(this) as common.UIAbilityContext;
112     let cacheDir = this.context.cacheDir;
113     let tempDir = fs.mkdtempSync(`${cacheDir}/XXXXXX`);
114     // Put the image URIs into the fileUris, and traverse and copy the images to the temporary folder.
115     for (let i = 0; i < fileUris.length; i++) {
116       let fileName = fileUris[i].split('/').pop();
117       let resourceFile: fs.File = fs.openSync(fileUris[i], fs.OpenMode.READ_ONLY);
118       fs.copyFileSync(resourceFile.fd, `${tempDir}/${fileName}`, 0);
119       fs.closeSync(resourceFile);
120     }
121     // Compress the temporary folder into test.zip.
122     let options: zlib.Options = {
123       level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION,
124       memLevel: zlib.MemLevel.MEM_LEVEL_DEFAULT,
125       strategy: zlib.CompressStrategy.COMPRESS_STRATEGY_DEFAULT_STRATEGY
126     };
127     let data = await zlib.compressFile(tempDir, `${cacheDir}/test.zip`, options);
128     // Delete the temporary folder.
129     fs.rmdirSync(tempDir);
130     // Place the generated .zip package in the transmission queue.
131     this.waitList.push(`${cacheDir}/test.zip`);
132   }
133   ```
134
135### Resumable Transfer
136
137To empower resumable download, both the application and the server must use proper technologies for collaboration. You do not need to implement the code of resumable download. Instead, you only need to properly configure the SDK.
138
139You can use the following APIs on the application side:
140
141- [@ohos.file.fs (File Management)](../reference/apis-core-file-kit/js-apis-file-fs.md): processes file upload operations, for example, reading file content, slicing files, and combining file slices.
142- [@ohos.file.hash (File Hash Processing)](../reference/apis-core-file-kit/js-apis-file-hash.md): calculates the MD5 value of a file and sends the value to the server for preprocessing. In this way, files can be transferred within seconds, with the accuracy and reliability ensured.
143- [@ohos.request (Upload and Download)](../reference/apis-basic-services-kit/js-apis-request.md): implements file upload and resumable upload.
144
145You can use the following technologies on the server:
146
147- Range support: The server must support requests carrying the **Range** field to facilitate resumable file upload and download.
148- File verification: Implement file verification to ensure that the file transfer can be resumed after an interruption.
149
150
151By combining technologies on the application side and server side, efficient and reliable resumable transfer can be implemented, delivering a better user experience and ensuring stable data transmission.
152
153This topic provides code snippet for resumable upload in the background upload scenario, which is described in the sample [Upload and Download](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Connectivity/UploadAndDownLoad). You can refer to the sample for the complete code.
154
155#### File Upload
156
157You can use the **request.agent** API in [@ohos.request (Upload and Download)](../reference/apis-basic-services-kit/js-apis-request.md) to implement automatic pause, resume, and retry operations in resumable upload scenarios. It frees you from manually slicing a file and recording the slice information. Figure 4 shows the flowchart.
158
159**Figure 4** Flowchart of resumable upload
160
161![Resumable upload](./figures/file-upload-and-download-pic4.png)
162
163To implement resumable upload, proceed as follows:
164
165The complete code is provided in [RequestUpload.ets](https://gitee.com/openharmony/applications_app_samples/blob/master/code/BasicFeature/Connectivity/UploadAndDownLoad/features/uploadanddownload/src/main/ets/upload/RequestUpload.ets).
166
1671. Import the modules.
168
169   ```ts
170   import common from '@ohos.app.ability.common';
171   import request from '@ohos.request';
172   ```
173
1742. Create an upload class.
175
176   ```ts
177   class Upload {
178     // Background task.
179     private backgroundTask: request.agent.Task | undefined = undefined;
180     // Storage URI before the task is created.
181     private waitList: Array<string> = [];
182     ...
183   }
184   ```
185
1863. Generate an MD5 value and upload it to the server for verification.
187
188   ```ts
189   async checkFileExist(fileUri: string): Promise<boolean> {
190     let httpRequest = http.createHttp();
191     // Generate an MD5 value.
192     let md5 = await hash.hash(fileUri, 'md5');
193     let requestOption: http.HttpRequestOptions = {
194       method: http.RequestMethod.POST,
195       extraData: {
196         'MD5': md5
197       }
198     }
199     let response = await httpRequest.request('http://XXX.XXX.XXX.XXX/XXXX', requestOption);
200     let result = response.result;
201     let flag = false;
202     ... // Determine whether the file exists based on the data returned by the server.
203     if (flag) {
204       return true;
205     } else {
206       return false;
207     }
208   }
209   ```
210
2114. Configure an agent and create a background upload task.
212
213   ```ts
214   private config: request.agent.Config = {
215     action: request.agent.Action.UPLOAD,
216     headers: HEADER,
217     url: '',
218     mode: request.agent.Mode.BACKGROUND,
219     method: 'POST',
220     title: 'upload',
221     network: request.agent.Network.ANY,
222     data: [],
223     token: 'UPLOAD_TOKEN'
224   }
225   ...
226   // Convert the URI.
227   private async getFilesAndData(cacheDir: string, fileUris: Array<string>): Promise<Array<request.agent.FormItem>> {
228   ...
229   }
230   // Create a background task to upload the file.
231   async createBackgroundTask(fileUris: Array<string>) {
232    // Obtain the URL.
233     this.config.url = 'http://XXX.XXX.XXX.XXX';
234     this.config.mode = request.agent.Mode.BACKGROUND;
235     let tempData = await this.getFilesAndData(this.context.cacheDir, fileUris);
236     // Check whether the file is empty.
237     for (let i = 0; i < tempData.length; i++) {
238       let flag = await this.checkFileExist(`${this.context.cacheDir}/${tempData[i].name}`);
239       if (!flag) {
240         this.config.data.push(tempData[i])
241       }
242     }
243     let isFileExist = await this.checkFileExist(`${this.context.cacheDir}/${this.config.data[0].name}`);
244     if (this.config.data.length === 0) {
245       return;
246     }
247     this.backgroundTask = await request.agent.create(this.context, this.config);
248   }
249   ```
250
2515. Start the task.
252
253   ```ts
254   await this.backgroundTask.start();
255   ```
256
2576. Pause the task.
258
259   ```ts
260   async pause() {
261     if (this.backgroundTask === undefined) {
262       return;
263     }
264     await this.backgroundTask.pause();
265   }
266   ```
267
2687. Resume the task.
269
270   ```ts
271   async resume() {
272     if (this.backgroundTask === undefined) {
273       return;
274     }
275     await this.backgroundTask.resume();
276   }
277   ```
278
279#### File Download
280
281You can call **request.agent** in resumable download scenarios. This API implements resumable download based on the **Range** field in the HTTP header. This field is automatically set when a task is resumed and requires no additional configuration.
282
283> **Introduction to Range**
284>
285> The **Range** field in the HTTP protocol allows the server to send an HTTP message
286> to the client to request part of the data rather than the entire resource.
287>
288> The format of the field is Range: \<unit>=\<start>-\<end>, where **\<unit>** indicates the range unit (bytes usually), and **\<start>** and **\<end>** indicate the start byte and end byte of the message, respectively.
289>
290> The syntax of **Range** is as follows:
291> ```ts
292> // From range-start to the end of the file.
293> Range: <unit>=<range-start>-
294> // From range-start to range-end.
295> Range: <unit>=<range-start>-<range-end>
296> // Multiple parts, separated by commas (,).
297> Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
298>
299> // Example: The file content after 1024 bytes is returned.
300> Range: bytes=1024-
301> ```
302> The server returns 206 Partial Content if it properly processes the message, and returns other response codes in the case of a failure. The table below lists the common response codes returned by the server.
303>
304> | Response Code | Common Cause |
305> | ------------------ | -----------------|
306> | 206 Partial Content | The server successfully processes the message and returns the requested range of data.|
307> | 416 Range Not Satisfiable | The range requested by the client is invalid.|
308> |200 OK | The server ignores the **Range** field in the header and returns the entire file.|
309>
310
311To implement resumable download, proceed as follows:
312
313The complete code is provided in [RequestDownload.ets](https://gitee.com/openharmony/applications_app_samples/blob/master/code/BasicFeature/Connectivity/UploadAndDownLoad/features/uploadanddownload/src/main/ets/download/RequestDownload.ets).
314
3151. Import the modules.
316
317   ```ts
318   import common from '@ohos.app.ability.common';
319   import request from '@ohos.request';
320   ```
321
3222. Create a download class.
323
324   ```ts
325   class Download {
326     // Storage URI before the task is created.
327     private waitList: Array<string[]> = [];
328     // Download task.
329     private downloadTask: request.agent.Task | undefined = undefined;
330     // Background task download list.
331     private backgroundDownloadTaskList: Array<request.agent.Task> = [];
332     ...
333   }
334   ```
335
3363. Configure an agent and create a background download task.
337
338   ```ts
339   async createBackgroundTask(downloadList: Array<string[]>) {
340     let splitUrl = url.split('//')[1].split('/');
341     let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
342     let downloadConfig: request.agent.Config = {
343       action: request.agent.Action.DOWNLOAD,
344       url: url,
345       method: 'POST',
346       title: 'download',
347       mode: request.agent.Mode.FOREGROUND, // Only background tasks can be resumed.
348       network: request.agent.Network.ANY,
349       saveas: `./${folder}/${splitUrl[splitUrl.length-1]}`,
350       overwrite: true
351     }
352     this.downloadTask = await request.agent.create(context, downloadConfig);
353     if (this.backgroundDownloadTaskList.findIndex(task => task.config.url === downTask.config.url) === -1) {
354       this.backgroundDownloadTaskList.push(downTask);
355     }
356   }
357   ```
358
3594. Start the task.
360
361   ```ts
362   ...
363   await downTask.start();
364   ...
365   ```
366
3675. Pause the task.
368
369   ```ts
370   async pause() {
371     if (this.backgroundDownloadTaskList.length === 0) {
372       return;
373     }
374     this.backgroundDownloadTaskList.forEach(async task => {
375       await task.pause();
376     })
377   }
378   ```
379
3806. Resume the task.
381
382   ```ts
383   async resume() {
384     if (this.backgroundDownloadTaskList.length === 0) {
385       return;
386     }
387     this.backgroundDownloadTaskList.forEach(async task => {
388       await task.resume();
389     })
390   }
391   ```
392
3937. Stop the task.
394
395   ```ts
396   async deleteAllBackTasks() {
397     if (this.backgroundDownloadTaskList.length > 0) {
398       this.backgroundDownloadTaskList.forEach(async task => {
399         await request.agent.remove(task.tid);
400       })
401       this.backgroundDownloadTaskList = [];
402     }
403   }
404   ```
405
406