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 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 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 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 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