Search code examples
androidkotlinmultipartform-datacronet

How to send a “multipart/form-data” POST in Android with CRONET?


Has anyone been able to accomplish sending a multipart/form-data POST in Android with CRONET yet? I have had no success trying to upload an image/png using a POST request to our server and am curious if anyone has.

notes : i need CRONET based solution only

Post Method Example:

val myBuilder = CronetEngine.Builder(context)
// Enable caching of HTTP data and
// other information like QUIC server information, HTTP/2 protocol and QUIC protocol.
val cronetEngine: CronetEngine = myBuilder
    .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024.toLong())
    .enableHttp2(true)
    .enableQuic(true)
    .build()
val executor: Executor = Executors.newSingleThreadExecutor()
val requestBuilder = cronetEngine.newUrlRequestBuilder(
    "FULL-URL",
    MyUrlRequestCallback(),
    executor
)
// Content-Type is required, removing it will cause Exception
requestBuilder.addHeader("Content-Type","application/json; charset=UTF-8")
requestBuilder.setHttpMethod("POST")
val myUploadDataProvider = MyUploadDataProvider()
requestBuilder.setUploadDataProvider(myUploadDataProvider,executor)
val request: UrlRequest = requestBuilder.build()
request.start()

MyUploadDataProvider Class:

import android.util.Log
import org.chromium.net.UploadDataProvider
import org.chromium.net.UploadDataSink
import java.lang.Exception
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets

private const val TAG = "MyUploadDataProvider"
//TODO replace username and passowrd "_user & _pass"
var string: String ="{\"username\":\"_user\",\"password\":\"_pass\"}"
val charset = StandardCharsets.UTF_8

class MyUploadDataProvider() : UploadDataProvider() {

    override fun getLength(): Long {
    val size:Long = string.length.toLong()
    Log.e(TAG,"Length = "+size)
    return size
    }

    override fun rewind(uploadDataSink: UploadDataSink?) {
    Log.e(TAG,"REWIND IS CALLED")
    uploadDataSink!!.onRewindSucceeded()
    }

    override fun read(uploadDataSink: UploadDataSink?, byteBuffer: ByteBuffer?) {
    Log.e(TAG,"READ IS CALLED")
    byteBuffer!!.put(string.toByteArray(charset))
    //byteBuffer.rewind()
    //For chunked uploads, true if this is the final read. It must be false for non-chunked uploads.
    uploadDataSink!!.onReadSucceeded(false)
    }

}

Solution

  • To upload a file, I would rather use create() methods from UploadDataProviders. With help of these methods, you can create an UploadDataProvider for uploading a File, FileDescriptor or even a byte array. For instance, to upload an empty array of bytes, I could do something as:

    ...
    val myUploadDataProvider = UploadDataProviders.create(byteArrayOf())
    ...
    

    Or, If I have a File object, I can also pass it directly into the create method:

    ...
    val myUploadDataProvider = UploadDataProviders.create(someFile)
    ...
    

    So, the whole code can look as follows:

    ...
        val myBuilder = CronetEngine.Builder(this)
        val cronetEngine: CronetEngine = myBuilder
                .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024.toLong())
                .enableHttp2(true)
                .enableQuic(true)
                .build()
        val executor: Executor = Executors.newSingleThreadExecutor()
        val requestBuilder = cronetEngine.newUrlRequestBuilder(
                "http://ptsv2.com/",
                object : UrlRequest.Callback() {
                    override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo?) {
                    }
    
                    override fun onReadCompleted(request: UrlRequest?, info: UrlResponseInfo?, byteBuffer: ByteBuffer?) {
    
                    }
    
                    override fun onFailed(request: UrlRequest?, info: UrlResponseInfo?, error: CronetException?) {
                    }
    
                    override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
                    }
    
                    override fun onRedirectReceived(request: UrlRequest?, info: UrlResponseInfo?, newLocationUrl: String?) {
    
                    }
    
                },
                executor
        )
        requestBuilder.addHeader("Content-Type","application/json; charset=UTF-8")
        requestBuilder.setHttpMethod("POST")
        val myUploadDataProvider = UploadDataProviders.create(byteArrayOf())
        requestBuilder.setUploadDataProvider(myUploadDataProvider,executor)
        val request: UrlRequest = requestBuilder.build()
        request.start()
    ...