I'm trying to do a file upload using Google Drive ...
# ktor
s
I'm trying to do a file upload using Google Drive API v3 It uses a
multipart/related
content type according to https://developers.google.com/drive/api/guides/manage-uploads#http_1 My code does not work. How to do this correctly? Did someone manage to upload to that API? Here's what I got so far:
Copy code
val response: HttpResponse = httpClient
    .post("<https://www.googleapis.com/drive/v3/files?uploadType=multipart>") {

    val boundary = "boundary_xyz"

    headers {
        append(HttpHeaders.ContentType, "multipart/related; boundary=$boundary")
    }

    setBody(
        buildString {
            /* Add metadata part */
            append("--$boundary\r\n")
            append("Content-Type: application/json; charset=UTF-8\r\n\r\n")
            append("{")
            append("\"name\": \"${path.substringAfterLast("/")}\"")
            // folderId?.let { append(", \"parents\": [\"$folderId\"]") }
            append("}")
            append("\r\n")

            /* Add file content part */
            append("--$boundary\r\n")
            append("Content-Type: application/octet-stream\r\n\r\n")
        }.toByteArray() + bytes + "\r\n--$boundary--".toByteArray()
    )
}
Returns with
Copy code
{
  "error": {
    "code": 400,
    "message": "Invalid JSON payload received. Unable to parse number.\n--boundary_xyz\r\nCont\n^",
    "errors": [
      {
        "message": "Invalid JSON payload received. Unable to parse number.\n--boundary_xyz\r\nCont\n^",
        "domain": "global",
        "reason": "parseError"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}
1
a
Can you try adding an extra \r\n before the boundary of the octet-stream? So
Copy code
/* Add file content part */
            append("--$boundary\r\n")
becomes:
Copy code
/* Add file content part */
            append("\r\n--$boundary\r\n")
s
Copy code
{
  "error": {
    "code": 400,
    "message": "Invalid JSON payload received. Unable to parse number.\n--boundary_xyz\r\nCont\n^",
    "errors": [
      {
        "message": "Invalid JSON payload received. Unable to parse number.\n--boundary_xyz\r\nCont\n^",
        "domain": "global",
        "reason": "parseError"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}
Looks like the identical message
I don't know why it expects a number. Google did not document this part of the API very well.
a
This is strange indeed. It appears that it doesn't notice the boundary after the JSON part.
s
Yes
a
Can you recreate it with a raw HTTP request? E.g. make a .http file in IntelliJ and write the request out manually? The byte content can be gibberish ofcourse.
You can also try specifying the Content-Length header of the json part to the length of the string of the content...
👍 1
s
Ah, that feature... https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html Never used it. Good opportunity 😄
a
I use it a lot lately, it's pretty great actually. Especially with API debugging. Manually forming the request until it works, and then expressing it in code.
💡 1
s
Still the same error, but the actual request is now indeed way more readable.
Copy code
{
  "error": {
    "code": 400,
    "message": "Invalid JSON payload received. Unable to parse number.\n--xxxxxxxxx\nContent-\n^",
    "errors": [
      {
        "message": "Invalid JSON payload received. Unable to parse number.\n--xxxxxxxxx\nContent-\n^",
        "domain": "global",
        "reason": "parseError"
      }
    ],
    "status": "INVALID_ARGUMENT"
  }
}
The multipart/related seems not to be supported. If I change to multipart/form-data the formatting gets way nicer.
a
True. But that’s just for formatting. The drive api requires “related”
s
Yes. The lack of documentation is frustrating. I guess I would need to use the JAVA tooling and catch with wireshark what it's actually sending
a
I was looking at the NodeJS source code, but also couldn't find anything interesting. The only thing is they use guids as boundaries... but it couldn't be that. Maybe something related to carriage returns, but that is also not reasonable.
s
Indeed. Thanks for your help! 🙂
This works using IDEA Http Client, but I didn't find a way to craft this
multipart/related
request with Ktor in a way that the Google Drive API will accept.
Copy code
POST <https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart>
Authorization: Bearer myAccessToken
Content-Type: multipart/related; boundary=xxxxxxxxx

--xxxxxxxxx
Content-Type: application/json; charset=UTF-8

{
"name": "photo_19.xmp"
}

--xxxxxxxxx
Content-Type: application/octet-stream

<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Ashampoo XMP Core 1.4.2">
  <rdf:RDF xmlns:rdf="<http://www.w3.org/1999/02/22-rdf-syntax-ns#>">
    <rdf:Description rdf:about=""
        xmlns:xmp="<http://ns.adobe.com/xap/1.0/>"
      xmp:Rating="3"/>
  </rdf:RDF>
</x:xmpmeta>

--xxxxxxxxx--
a
What is the difference when you do it with ktor client?
a
Can you intercept the network requests with a traffic analyzing tool to compare both requests?
s
The invalid argument errors
Can you intercept the network requests with a traffic analyzing tool to compare both requests?
Which one?
a
Do you mean the tool?
s
Yes, what do you recommend? Wireshark?
a
Yes, WireShark
s
Yes, WireShark
I assumed that there may be better tools around than this and you might have an recommendation. I would not be suprised if that's also an IDEA Ultimate feature 😄
Isn't there an option for Ktor to log what exactly it's sending? (without having slf4j in the project)
a
Ktor cannot log precisely what it's sending because the actual messages are sent by the engines. In theory, the CIO engine can log the exact messages.
s
Ok, I tried looking into that using Wireshark, but since it's all TLS encrypted I can't see anything.
a
Just have Ktor have it sent to some simple echo server which outputs the request you make. No need for Wireshark.
👍 1
s
Ah, great idea. Thank you!
👍 1