rybalkinsd / kohttp

Kotlin DSL http client
https://kohttp.gitbook.io
Apache License 2.0
478 stars 42 forks source link

Have problem when use multipartBody #125

Closed rraayy closed 5 years ago

rraayy commented 5 years ago

Hello,

I have a problem when using kohttp to upload an image when using multipartBody I have to upload an image and also needs some data like "name" and "id" like the attached image in Postman. I also need to add "Authorization" in the header. 螢幕快照 2019-07-06 16 21 14

below is my code

val path = "$serverURL"
        val response = httpPost {
            url(path)
            header {
                "content-type" to "multipart/form-data"
                "Authorization" to "Bearer " + myToken
            }
            multipartBody {
                +form("image",image)
                "name" to name
                "id" to "123"

            }
        }.eager()

The image cannot upload and seems like name & id also not send to the server. Can you help me if there is any wrong when I use kohttp to upload the file?

rybalkinsd commented 5 years ago

Hi @rraayy . Thanks for your feedback! I will try to investigate your problem soon. Could you, please, clarify what is a type of image in your code?

Until I will check it, you’re able to use LoggingInterceptor to check the request https://kohttp.gitbook.io/docs/#interceptors

rybalkinsd commented 5 years ago

@rraayy , after a quick check I found the possible reason:

to is not defined in MultipartBodyContext scope. As a result, your code will invoke function from Tuples.kt

Inside MultipartBodyContext it is possible to use FormDataPart or form constructions. Please check the following code.

val image = ...
val response = httpPost {
    url( ... )
    header {
        "content-type" to "multipart/form-data"
        "Authorization" to "Bearer " + xxx
    }
    multipartBody {
        +FormDataPart("name", null, RequestBody.create(null, "Andy"))
        +FormDataPart("id", null, RequestBody.create(null, "123"))
        +form("image", image)
    }
}

Please notice me if it solves your problem

rraayy commented 5 years ago

@rybalkinsd , thanks for the reply. I had tried your code but seems still cannot work and My image type is "image/jpeg". I will try to add LoggingInterceptor in my code and find out the request.

rybalkinsd commented 5 years ago

@rraayy here is also a blog post about upload to Google Drive that may help you https://medium.com/@sergei.rybalkin/upload-file-to-google-drive-with-kotlin-931cec5252c1

rybalkinsd commented 5 years ago

@rraayy Here is also an example of the multipart upload https://gist.github.com/rybalkinsd/a6b1cb20fab17d2c7a092c7b4c94de72

Also Google Drive example, 'cause it looks like the closest to your case

rraayy commented 5 years ago

@rybalkinsd Thanks, I will try this one when I came home and let you know if it works or not.

rraayy commented 5 years ago

Still cannot work..T. T, below is my code

        val response = httpPost {
            url(MYPATH)
            header {
                "Authorization" to "Bearer " +MYTOKEN
            }
            multipartBody {
                +FormDataPart("data", null, RequestBody.create(
                    "application/json".toMediaType(),
                    json {
                        "name" to name
                    }
                ))
                +form("image", image)
            }

        }.eager()

have no idea ...why cannot work well. Using okhttp below is working

val MEDIA_TYPE_JPG = "image/jpeg".toMediaTypeOrNull()

        var multipartBody = MultipartBody.Builder()
        multipartBody.setType(MultipartBody.FORM)
        multipartBody.addFormDataPart("image", "image.jpg", RequestBody.create(MEDIA_TYPE_JPG, image))
        multipartBody.addFormDataPart("name", name)

        val multipartSendBody = multipartBody.build()

        val request = Request.Builder()
                .url(MYPATH)
                .addHeader("Authorization", "Bearer "+MYTOKEN)
                .post(multipartSendBody)
                .build()

        client.newCall(request).execute().use { response -> XXXXX }
rybalkinsd commented 5 years ago

@rraayy Thanks for posting the comparison with okhttp! Now it's much easier to detect the difference!

Let me do a comparison:

  1. Request body type: MultipartBody.FORM is provided explicitly in the second example. However, we treat it as multipart/mixed b/c body parts are different.

  2. +form ( ... ) uses null as content type by default. In the second example, MEDIA_TYPE_JPG is provided explicitly.

  3. +form("image", image) will use your exact file.name, in the second example image.jpg is provided explicitly (probably it's not your case, but want to mention it).

  4. addFormDataPart("name", name) should be provided in a different way:

  5. Order of parts inside the multipart body. It is different in the provided examples. It could lead to a problem on several servers.

As to conclude, I would recommend changing your request to the following:

   val response = httpPost {
            url(MYPATH)
            header {
                "Authorization" to "Bearer " + MYTOKEN
            }

            multipartBody(MULTIPART_FORM_TYPE) {
                +FormDataPart("image", "image.jpg", RequestBody.create(MEDIA_TYPE_JPG, image))
                +FormDataPart("name", null, RequestBody.create(null, name))
            }
    }
rybalkinsd commented 5 years ago

We will discuss the multipart body syntax with the project team to figure out our options to improve. Suggestions are very welcome

rybalkinsd commented 5 years ago

@rraayy Please let me know if the problem is solved

rraayy commented 5 years ago

@rybalkinsd Good news!!! Your recommend code works well!! Thank you so much. Below is the code I used.

        val MEDIA_TYPE_JPG = "image/jpeg".toMediaTypeOrNull()
        val response = httpPost {
            url(MYPATH)
            header {
                "Authorization" to "Bearer " + MYTOKEN           }

            multipartBody("multipart/form-data") {
                +FormDataPart("image", "image.jpg", RequestBody.create(MEDIA_TYPE_JPG, image))
                +FormDataPart("name", null, RequestBody.create(null, name))
            }
        }.eager()
rybalkinsd commented 5 years ago

Closing this issue. Enhancements connected with UX feedback will be implemented in #126