如何在 Retrofit 中将 InputStream 作为请求的主体发布?

Posted

技术标签:

【中文标题】如何在 Retrofit 中将 InputStream 作为请求的主体发布?【英文标题】:How to POST InputStream as the body of a request in Retrofit? 【发布时间】:2014-04-27 20:16:21 【问题描述】:

我正在尝试将正文作为 InputStream 进行 POST,其内容如下:

@POST("/build")
@Headers("Content-Type: application/tar")
Response build(@Query("t") String tag,
               @Query("q") boolean quiet,
               @Query("nocache") boolean nocache,
               @Body TypedInput inputStream);

在这种情况下,InputStream 来自压缩的 tar 文件。

POST InputStream 的正确方法是什么?

【问题讨论】:

类似问题:***.com/questions/45123696/… 【参考方案1】:

您可以使用Multipart 上传 inputStream。

@Multipart
@POST("pictures")
suspend fun uploadPicture(
        @Part part: MultipartBody.Part
): NetworkPicture

然后在你的存储库类中:

suspend fun upload(inputStream: InputStream) 
   val part = MultipartBody.Part.createFormData(
         "pic", "myPic", RequestBody.create(
              MediaType.parse("image/*"),
              inputStream.readBytes()
          )
   )
   uploadPicture(part)

如果您想了解如何获取图像 Uri,请查看此答案:https://***.com/a/61592000/10030693

【讨论】:

【参考方案2】:

TypedInputInputStream 的包装器,其中包含用于发出请求的长度和内容类型等元数据。您需要做的就是提供一个实现TypedInput 的类,它传递了您的输入流。

class TarFileInput implements TypedInput 
  @Override public InputStream in() 
    return /*your input stream here*/;
  

  // other methods...

确保根据您从中流式传输内容的文件类型为length()mimeType() 传递适当的返回值。

当您调用 build 方法时,您还可以选择将其作为匿名实现传递。

【讨论】:

感谢您的回复,但我在使用 TypedInput 时遇到了问题。我在下面记录了我的解决方案以供将来参考。 TypedInput 好像已经不可用了,那么 inputStream 的解决方法是什么? @gropapa 的问题我迟到了一年,但对于其他想知道同样事情的人(比如我):将InputStream 转换为byte[],然后可以用来为 Retrofit 2.x 中的文件上传构造一个 RequestBody。详情请见this link。【参考方案3】:

我在这里想出的唯一解决方案是使用 TypeFile 类:

TypedFile tarTypeFile = new TypedFile("application/tar", myFile);

和接口(这次没有显式设置 Content-Type 标头):

@POST("/build")
Response build(@Query("t") String tag,
               @Query("q") boolean quiet,
               @Query("nocache") boolean nocache,
               @Body TypedInput inputStream);

使用我自己的 TypedInput 实现会导致一个模糊的 EOF 异常,即使我提供了 length()。

public class TarArchive implements TypedInput 

    private File file;

    public TarArchive(File file) 
        this.file = file;
    

    public String mimeType() 
        return "application/tar";
    

    public long length() 
        return this.file.length();
    

    public InputStream in() throws IOException 
        return new FileInputStream(this.file);
    

此外,在解决此问题时,我尝试使用最新的 Apache Http 客户端而不是 OkHttp,这导致“Content-Length 标头已存在”错误,即使我没有明确设置该标头。

【讨论】:

您的TypedInput 实现不适用于文件上传,因为该用例需要一个TypedOutput,正如@BertHartmann's answer 中所指出的那样。不过,错误消息可能很不清楚。【参考方案4】:

根据http://square.github.io/retrofit/ 的Multipart 部分,您需要使用TypedOutput 而不是TypedInput。一旦我实现了 TypedOutput 类,按照他们的分段上传示例对我来说效果很好。

【讨论】:

【参考方案5】:

我的解决方案是实现TypedOutput

public class TypedStream implements TypedOutput

    private Uri uri;

    public TypedStream(Uri uri)
        this.uri = uri;
    

    @Override
    public String fileName() 
        return null;
    

    @Override
    public String mimeType() 
        return getContentResolver().getType(uri);
    

    @Override
    public long length() 
        return -1;
    

    @Override
    public void writeTo(OutputStream out) throws IOException 
        Utils.copyStream(getContentResolver().openInputStream(uri), out);
    

【讨论】:

以上是关于如何在 Retrofit 中将 InputStream 作为请求的主体发布?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中将 InputStream 转换为字符串?

如何在 Java 中将 InputStream 转换为字符串?

如何在 Ktor 中设置类似于 Retrofit 的`Retrofit.Builder().baseUrl(baseUrl) 的 basePath?

如何使用拦截器在 Retrofit 2.0 中添加标头?

Retrofit2源码解读

如何使用 Retrofit 库在 Android 中下载文件?