Search code examples
kotlinktor-clientstability

In Kotlin Android KTor POST request with multi part form data is returning Malformed FormData Request


I am trying to access Stability AI in android using Ktor Client the code looks like this

suspend fun generateImageOfText(text: String,aspectRatio: String = "1:1", apiKey: String): Result {
    return try {
        val response = httpClient.post("https://api.stability.ai/v2beta/stable-image/generate/ultra") {
            header(HttpHeaders.Authorization, "Bearer $apiKey")
            header(HttpHeaders.Accept, "application/json")
            setBody(MultiPartFormDataContent(
                formData {
                    append("prompt", text)
                },
                boundary = "WebAppBoundary"
            ))
        }
        val resultText = response.bodyAsText()
        Log.d("APIConnector", "generateImageOfText: status ${response.status} result $resultText")
        Result.Success(resultText)
    }catch (e: Exception){
        e.printStackTrace()
        (Result.Error(e.message ?: "Unknown error"))
    }
}

which results in

400 Bad Request response {"success":false,"message":"Malformed FormData request. Content-Disposition header in FormData part is missing a name."}

Stability API requires us to provide it with Multi part form data but where should I include this content-disposition header? I did check in official documentation where it gives us an example to upload an image file which looks like this

val client = HttpClient(CIO)

val response: HttpResponse = client.post("http://localhost:8080/upload") {
    setBody(MultiPartFormDataContent(
        formData {
            append("description", "Ktor logo")
            append("image", File("ktor_logo.png").readBytes(), Headers.build {
                append(HttpHeaders.ContentType, "image/png")
                append(HttpHeaders.ContentDisposition, "filename=\"ktor_logo.png\"")
            })
        },
        boundary = "WebAppBoundary"
    )
    )
    onUpload { bytesSentTotal, contentLength ->
        println("Sent $bytesSentTotal bytes from $contentLength")
    }
}

in this example a file is getting uploaded and it's file name is sent using Content-Disposition header but in my case I do not have any file to upload still I did try to pass in a string as name but It gave the same error, API is working in postman with the same parameters

EDIT 1: I have tried to include Content Type in my formData block but it was of no use

formData {
     append("prompt", text, Headers.build {
         append(HttpHeaders.ContentType, "text/plain")
     }) }

Edit 2: After adding additional quotes as suggested in the answer and comments I was able to move forward thanks a lot, final code looks like this

suspend fun generateImageOfText(text: String,aspectRatio: String = "1:1", apiKey: String): Result {
        return try {
            val response = httpClient.post("https://api.stability.ai/v2beta/stable-image/generate/core") {
                header(HttpHeaders.Authorization, "Bearer $apiKey")
                header(HttpHeaders.Accept, "application/json")
                setBody(MultiPartFormDataContent(
                    formData {
                        append("\"prompt\"", text, Headers.build {
                            append(HttpHeaders.ContentDisposition, "form-data; name=\"prompt\"")
                        })
                    }
                ))
            }
            val result: Base64Result = response.body()
            Result.Success(result)
        }catch (e: Exception){
            e.printStackTrace()
            (Result.Error(e.message ?: "Unknown error"))
        }
    }

Solution

  • I had the same problem in C# and also received the error message Malformed FormData request. Content-Disposition header in FormData part is missing a name. For some strange reason I had to wrap the parameter names in two quotes.

    var prompt = "Cat on the moon";
    var format = "3:2";
    
    using var content = new MultipartFormDataContent()
    {
        { new StringContent(prompt), "\"prompt\"" },
        { new StringContent("png"), "\"output_format\"" },
        { new StringContent(format), "\"aspect_ratio\"" }
    };
    
    var url = "https://api.stability.ai/v2beta/stable-image/generate/ultra";
    var request = new HttpRequestMessage(HttpMethod.Post, url)
    {
        Content = content
    };
    
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("image/*"));
    var response = await _httpClient.SendAsync(request);