Вопрос

I'm attempting to do a POST with the body being an InputStream with something like this:

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

In this case the InputStream is from a compressed tar file.

What's the proper way to POST an InputStream?

Это было полезно?

Решение 3

The only solution I came up with here was to use the TypeFile class:

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

and the interface (without explicitly setting the Content-Type header this time):

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

Using my own implementation of TypedInput resulted in a vague EOF exception even while I provided the 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);
    }
}

Also, while troubleshooting this issue I tried using the latest Apache Http client instead of OkHttp which resulted in a "Content-Length header already present" error even though I wasn't explicitly setting that header.

Другие советы

You can upload inputStream using Multipart.

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

Then in perhaps your repository class:

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

If you want to find out how to get an image Uri, check this answer: https://stackoverflow.com/a/61592000/10030693

TypedInput is a wrapper around an InputStream that has metadata such as length and content type which is used in making the request. All you need to do is provide a class that implements TypedInput which passed your input stream.

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

  // other methods...
}

Be sure you pass the appropriate return values for length() and mimeType() based on the type of file from which you are streaming content.

You can also optionally pass it as an anonymous implementation when you are calling your build method.

According to the Multipart section of http://square.github.io/retrofit/ you'll want to use TypedOutput instead of TypedInput. Following their examples for multipart uploads worked fine for me once I had implemented a TypedOutput class.

My solution was to implement 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);
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top