改造上传将表单数据存储在上传文件中,破坏它

Posted

技术标签:

【中文标题】改造上传将表单数据存储在上传文件中,破坏它【英文标题】:Retrofit upload is storing form data inside the upload file, corrupting it 【发布时间】:2020-07-16 13:46:38 【问题描述】:

我正在使用 Retrofit 2 通过 Azure 的 REST API 将音频文件上传到 Azure Blob 存储服务。

上传似乎可以正常工作,但存储在 Azure blob 容器中的文件已损坏,因为它包含似乎是 HTTP 标头的音频数据以及音频数据。例如,这些是一个上传文件的内容:

--3c88cdb1-5946-432d-a129-cc8e930d014c
Content-Disposition: form-data; name="tape"; 
filename="/data/user/0/blahblah.mp4"
Content-Type: audio/mp4
Content-Length: 8365

...expected binary data blah blah blah ....
--3c88cdb1-5946-432d-a129-cc8e930d014c--

我做错了什么?

我的上传功能是这样的:

    val tapeFile = File(fileName)
    val tapePart = tapeFile.asRequestBody("audio/mp4".toMediaType())
    val tapeBodyPart = MultipartBody.Part.createFormData("tape",tapeFile.absolutePath, tapePart)
    tapeAzureWebService.uploadTape(url, tapeBodyPart).enqueue(object : Callback<ResponseBody> 
        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) 
            if (response.isSuccessful)  
    etc etc

我的Retrofit界面界面是这样的:

@Multipart
@PUT
fun uploadTape(@Url url: String,
               @Part tape: MultipartBody.Part): Call<ResponseBody>

(它使用@URL,因为我使用的是 Azure SAS,动态 URL 和身份验证作为一系列查询字符串嵌入在 URL 中,效果很好,对于任何偶然发现此问题的人来说都是一个简洁的提示,通过顺便说一句,因为它会阻止 Retrofit 对 URL 和查询进行编码。)

我的 OKHttp 客户端看起来像这样,添加了一些 Azure 要求的标头:

class TapeAzureWebServiceAPI 

  fun service() : TapeAzureWebService 

    val headerInterceptor = object: Interceptor 
        override fun intercept(chain: Interceptor.Chain): Response 
            val original = chain.request()
            val requestBuilder = original.newBuilder()
                    .header("x-ms-version", "2015-12-11")
                    .header("x-ms-blob-type","BlockBlob")
            val request = requestBuilder.build()
            return chain.proceed(request)
        
    

    val loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger 
        override fun log(message: String) 
            logI("retrofit: $message")
        
    ).setLevel(HttpLoggingInterceptor.Level.BODY)

    val client : OkHttpClient = OkHttpClient.Builder().apply 
        this.addInterceptor(headerInterceptor)
        this.addInterceptor(loggingInterceptor)
    .build()

    val retrofit = Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(AZURE_URL)
            .client(client)
            .build()
    return retrofit.create(TapeAzureWebService::class.java)
  

如果我使用简单的 RequestBody 而不是多部分表单,我仍然会收到相同的音频文件损坏,尽管音频文件中的标头较少。

我已经看了很长时间,但我不知道这是否是我在 Retrofit 中做错了,Azure 是否需要不同的标头,或者 Azure 是否根本不喜欢多部分表单数据。

谢谢

约翰

【问题讨论】:

whether Azure simply doesn't like multipart form data - 就是这个。无论您上传什么,Azure 存储都会按原样保存。 有没有办法通过改造上传,或者我必须使用其他一些库/技术?顺便说一句,感谢您的快速回复! 不幸的是我不熟悉改造,所以我可以回答这个问题。但是,您需要做的是在 Azure 存储中上传原始字节流,而不将其转换为多部分表单数据。 谢谢。这为我指明了正确的方向。 或许可以试试RequestBody?基本上只使用来自val tapePart = tapeFile.asRequestBody("audio/mp4".toMediaType())tapePart 【参考方案1】:

删除@Multipart 只需添加,

@Headers(  "x-ms-blob-type: BlockBlob", "x-ms-blob-content-type: image/png")
@PUT
suspend fun uploadDocument(@Url url: String, @Body request: RequestBody)

并将请求正文传递为,

val mediaType = "image/png".toMediaTypeOrNull()
val body = yourImageFile.asRequestBody(mediaType)

【讨论】:

以上是关于改造上传将表单数据存储在上传文件中,破坏它的主要内容,如果未能解决你的问题,请参考以下文章

通过压缩将HTTP发布多部分/表单数据流式传输并上传到存储中?

21文件上传/下载

添加到 innerHTML 而不破坏表单内容

文件上传和下载

如何将表单数据和此 URL 保存到数据库中.. 我可以将文件上传到 cloudinary

结合jQuery文件上传和表单到mysql