The "basic operation" uploaded by OkHttp breakpoint is really unknown?

1. Foreword

Students often ask: how to upload files at breakpoints?

Breakpoint upload / download is a common scenario on the client. When we need to upload or download a large file, we will consider using breakpoint continuation.

The biggest difference between breakpoint upload and breakpoint download lies in the record of breakpoint location. The upload record is on the server and the download record is on the client. Therefore, the client needs to get the breakpoint location of the file through the interface before uploading, and then jump the file input stream to the breakpoint location during uploading

2. Preparatory work

For file upload, it is actually to open the input stream of the file, constantly read the data into the byte array, and then write it to the server; What the client needs to do is skip the uploaded part, that is, directly jump to the breakpoint position, so that the data can be read from the breakpoint position, which achieves the purpose of breakpoint upload.

The pseudo code is as follows:

String filePath = "...";
long skipSize = 100;  //Suppose the breakpoint location is 100 byte s
InputStream input = input = new FileInputStream(filePath);
input.skip(skipSize)  //Jump to breakpoint location

However, OkHttp does not directly provide a method to set breakpoints, so the client needs to customize the RequestBody, named FileRequestBody, as follows:

//Some code has been omitted to simplify reading
public class FileRequestBody extends RequestBody {

    private final File file;
    private final long skipSize;  //Breakpoint location
    private final MediaType mediaType;

    public FileRequestBody(File file, long skipSize, @Nullable MediaType mediaType) {
        this.file = file;
        this.skipSize = skipSize;
        this.mediaType = mediaType;
    }

    @Override
    public long contentLength() throws IOException {
        return file.length() - skipSize;
    }

    @Override
    public void writeTo(@NotNull BufferedSink sink) throws IOException {
        InputStream input = null;
        Source source = null;
        try {
            input = new FileInputStream(file);
            if (skipSize > 0) {
                input.skip(skipSize); //Jump to breakpoint
            }
            source = Okio.source(input);
            sink.writeAll(source);
        } finally {
            OkHttpCompat.closeQuietly(source, input);
        }
    }
}

For ease of reading, part of the source code is omitted above, FileRequestBody class complete source code

With the FileRequestBody class, we only need to pass in a breakpoint location, and the rest is the same as ordinary file upload. Next, go directly to the code implementation.

3. Code implementation

3.1 get breakpoint location

First, the server needs to provide an interface to find the unfinished task list uploaded by the user through userId. The code is as follows:

RxHttp.get("/.../getToUploadTask")
    .add("userId", "88888888")
    .asList<ToUploadTask>()
    .subscribe({
        //The callback is successful. Here you can get the list < touploadtask > through it
    }, {
        //Exception callback
    });

The ToUploadTask class is as follows:

//Tasks to be uploaded
data class ToUploadTask(
    val md5: String,          //md5 of the file, used to verify the uniqueness of the file
    val filePath: String,     //The absolute path of the file on the client
    val skipSize: Long = 0    //Breakpoint location
)

Note: md5 and filePath parameters need to be passed by the client to the server during file upload to verify the file and prevent file disorder

3.2 breakpoint upload

With the task to be uploaded, the client can perform breakpoint upload. The OkHttp code is as follows:

fun uploadFile(uploadTask: ToUploadTask) {
    //1. Verify whether the file exists
    val file = File(uploadTask.filePath)
    if (!file.exists() && !file.isFile) return
    //2. Verify the md5 value of the file
    val fileMd5 = FileUtils.getFileMD5ToString(file)
    if (!fileMd5.equals(uploadTask.md5)) return
    //3. Build request body
    val fileRequestBody = FileRequestBody(file, uploadTask.skipSize, BuildUtil.getMediaType(file.name))
    val multipartBody = MultipartBody.Builder()
        .addFormDataPart("userId", "88888888")
        .addFormDataPart("md5", fileMd5)
        .addFormDataPart("filePath", file.absolutePath)
        .addFormDataPart("file", file.name, fileRequestBody) //Add file body
        .build()
    //4. Build request
    val request = Request.Builder()
        .url("/.../uploadFile")
        .post(multipartBody)
        .build()
    //5. Execution request
    val okClient = OkHttpClient.Builder().build()
    okClient.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            //Exception callback
        }
        override fun onResponse(call: Call, response: Response) {
            //Successful callback
        }
    })
}

Of course, considering that few people will directly use OkHttp, the implementation code of {RxHttp} is also posted here. It is very simple. Just build an UpFile object to easily monitor the upload progress. The code is as follows:

fun uploadFile(uploadTask: ToUploadTask) {                            
    //1. Verify whether the file exists                                                      
    val file = File(uploadTask.filePath)                               
    if (!file.exists() && !file.isFile) return                         
    //2. Verify the md5 value of the file                                                      
    val fileMd5 = FileUtils.getFileMD5ToString(file)                   
    if (!fileMd5.equals(uploadTask.md5)) return                        
    val upFile = UpFile("file", file, file.name, uploadTask.skipSize)  
    //3. Direct upload
    RxHttp.postForm("/.../uploadFile")    
        .add("userId", "88888888")                                     
        .add("md5", fileMd5)                                           
        .add("filePath", file.absolutePath)                            
        .addFile(upFile)                                               
        .upload(AndroidSchedulers.mainThread()) {                       
            //Upload progress callback           
        }                                                              
        .asString()                                                    
        .subscribe({                                                    
            //Successful callback                                                            
        }, {                                                            
            //Exception callback                                                           
        })                                                             
}                                                                      

4. Summary

Compared with ordinary file upload, the client has one more breakpoint setting. Most of the workload is on the server. The server not only needs to process the file splicing logic, but also needs to record the unfinished tasks and expose them to the client through the interface.

Keywords: Android OkHttp Design Pattern

Added by homer09001 on Sat, 15 Jan 2022 22:29:57 +0200