Okhttp: [Feature Request] - RequestBody supports InputStream

Created on 5 Sep 2017  Â·  11Comments  Â·  Source: square/okhttp

I would like to submit a PR to add support for InputStreams in the RequestBody class. As of now, RequestBody supports the following:

  • create(MediaType contentType, String content)
  • create(MediaType contentType, ByteString content)
  • create(MediaType contentType, byte[] content)
  • create(MediaType contentType, byte[] content, final int offset, final int byteCount)
  • create(MediaType contentType, File file)

I would like to go one step further and add support for InputStream:

  • create(MediaType contentType, Inputream inputStream)

Similar to https://stackoverflow.com/a/25384793:

  public static RequestBody create(final @Nullable MediaType contentType, final InputStream inputStream) {
    if (inputStream == null) throw new NullPointerException("inputStream == null");

    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return inputStream.available() == 0 ? -1 : inputStream.available();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(inputStream);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }

This would be nice to have since Okio.source support both File and InputStream.

Most helpful comment

@JakeWharton I know it is not an Android library. I figured after the first comment, I would have to keep this code locally and not be able to make a PR. Something like this:

public class InputStreamRequestBody extends RequestBody {
    private final InputStream inputStream;
    private final MediaType contentType;

    public InputStreamRequestBody(MediaType contentType, InputStream inputStream) {
        if (inputStream == null) throw new NullPointerException("inputStream == null");
        this.contentType = contentType;
        this.inputStream = inputStream;
    }

    @Nullable
    @Override
    public MediaType contentType() {
        return contentType;
    }

    @Override
    public long contentLength() throws IOException {
        return inputStream.available() == 0 ? -1 : inputStream.available();
    }

    @Override
    public void writeTo(@NonNull BufferedSink sink) throws IOException {
        Source source = null;
        try {
            source = Okio.source(inputStream);
            sink.writeAll(source);
        } finally {
            Util.closeQuietly(source);
        }
    }
}

Based on your changes, @JakeWharton, to be changed to handle Uri:

public class InputStreamRequestBody extends RequestBody {
    private final MediaType contentType;
    private final ContentResolver contentResolver;
    private final Uri uri;

    public InputStreamRequestBody(MediaType contentType, ContentResolver contentResolver, Uri uri) {
        if (uri == null) throw new NullPointerException("uri == null");
        this.contentType = contentType;
        this.contentResolver = contentResolver;
        this.uri = uri;
    }

    @Nullable
    @Override
    public MediaType contentType() {
        return contentType;
    }

    @Override
    public long contentLength() throws IOException {
        return -1;
    }

    @Override
    public void writeTo(@NonNull BufferedSink sink) throws IOException {
        sink.writeAll(Okio.source(contentResolver.openInputStream(uri)));
    }
}

All 11 comments

A RequestBody occasionally needs to be written multiple times, which this
would not allow. The current overloads all create instances which support
this, and I think we intentionally want to make it hard to create one that
does not support this.

On Tue, Sep 5, 2017 at 5:21 PM Jared Burrows notifications@github.com
wrote:

I would like to submit a PR to add support for InputStreams in the
RequestBody class. As of now, RequestBody supports the following:

  • create(MediaType contentType, String content)
  • create(MediaType contentType, ByteString content)
  • create(MediaType contentType, byte[] content)
  • create(MediaType contentType, byte[] content, final int offset,
    final int byteCount)
  • create(MediaType contentType, File file)

I would like to go one step further and add support for InputStream:

  • create(MediaType contentType, Inputream inputStream)

Similar to https://stackoverflow.com/a/25384793:

public static RequestBody create(final @Nullable MediaType contentType, final File file) {
if (inputStream == null) throw new NullPointerException("inputStream == null");

return new RequestBody() {
  @Override public @Nullable MediaType contentType() {
    return contentType;
  }

  @Override public long contentLength() {
    return inputStream.available() == 0 ? -1 : inputStream.available();
  }

  @Override public void writeTo(BufferedSink sink) throws IOException {
    Source source = null;
    try {
      source = Okio.source(inputStream);
      sink.writeAll(source);
    } finally {
      Util.closeQuietly(source);
    }
  }
};

}

This would be nice to have since Okio.source support both File and
InputStream.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/square/okhttp/issues/3585, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAEEEZFuHECAXOV5XnYySXzNGKCQ6uqTks5sfbtKgaJpZM4PNii9
.

Are RequestBodys written multiple times on retries?

What would you suggest for contentResolver.openInputStream(uri)? Save it to a file first then use create(MediaType contentType, File file)? Even that uses Okio.source when uses a FileInputStream.

Inside of writeTo call that method and then call
sink.writeAll(Okio.source(steam)).

On Tue, Sep 5, 2017 at 5:46 PM Jared Burrows notifications@github.com
wrote:

Are RequestBodys written multiple times on retries?

What would you suggest for contentResolver.openInputStream(uri)? Save it
to a file first then use create(MediaType contentType, File file)? Even
that uses Okio.source when uses a FileInputStream.

—
You are receiving this because you commented.

Reply to this email directly, view it on GitHub
https://github.com/square/okhttp/issues/3585#issuecomment-327313068, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAEEEeFVXL3lWZyw8tf-Kd5UIza8_1eJks5sfcFMgaJpZM4PNii9
.

Also, request bodies may be written zero times, in which case this would leak the input stream.

@swankjesse Maybe I could pass a Uri instead of InputStream in the class to prevent leaking using.

@JakeWharton If I call contentResolver.openInputStream(uri) inside of writeTo, how do I update the long contentLength() before the request is made?

OkHttp isn't an Android library and can't reference Uri. Use -1 for length.

On Tue, Sep 5, 2017 at 6:06 PM Jared Burrows notifications@github.com
wrote:

@swankjesse https://github.com/swankjesse Maybe I could pass a Uri
instead of InputStream in the class to prevent leaking using.

@JakeWharton https://github.com/jakewharton If I call
contentResolver.openInputStream(uri) inside of writeTo, how do I update
the long contentLength() before the request is made?

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/square/okhttp/issues/3585#issuecomment-327317191, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAEEEU77khg3MDG3YOeq1wf8bFp61jVvks5sfcXGgaJpZM4PNii9
.

@JakeWharton I know it is not an Android library. I figured after the first comment, I would have to keep this code locally and not be able to make a PR. Something like this:

public class InputStreamRequestBody extends RequestBody {
    private final InputStream inputStream;
    private final MediaType contentType;

    public InputStreamRequestBody(MediaType contentType, InputStream inputStream) {
        if (inputStream == null) throw new NullPointerException("inputStream == null");
        this.contentType = contentType;
        this.inputStream = inputStream;
    }

    @Nullable
    @Override
    public MediaType contentType() {
        return contentType;
    }

    @Override
    public long contentLength() throws IOException {
        return inputStream.available() == 0 ? -1 : inputStream.available();
    }

    @Override
    public void writeTo(@NonNull BufferedSink sink) throws IOException {
        Source source = null;
        try {
            source = Okio.source(inputStream);
            sink.writeAll(source);
        } finally {
            Util.closeQuietly(source);
        }
    }
}

Based on your changes, @JakeWharton, to be changed to handle Uri:

public class InputStreamRequestBody extends RequestBody {
    private final MediaType contentType;
    private final ContentResolver contentResolver;
    private final Uri uri;

    public InputStreamRequestBody(MediaType contentType, ContentResolver contentResolver, Uri uri) {
        if (uri == null) throw new NullPointerException("uri == null");
        this.contentType = contentType;
        this.contentResolver = contentResolver;
        this.uri = uri;
    }

    @Nullable
    @Override
    public MediaType contentType() {
        return contentType;
    }

    @Override
    public long contentLength() throws IOException {
        return -1;
    }

    @Override
    public void writeTo(@NonNull BufferedSink sink) throws IOException {
        sink.writeAll(Okio.source(contentResolver.openInputStream(uri)));
    }
}

Ah, yes. Definitely do that to make it reusable in your own code. Don't
forget try-with-resources:

try (Source source = Okio.source(contentResolver.openInputStream(uri))) {
sink.writeAll(source);
}

On Tue, Sep 5, 2017 at 6:16 PM Jared Burrows notifications@github.com
wrote:

@JakeWharton https://github.com/jakewharton I know it is not an Android
library. I figured after the first comment, I would have to keep this code
locally and not be able to make a PR. Something like this:

public class InputStreamRequestBody extends RequestBody {
private final InputStream inputStream;
private final MediaType contentType;

public InputStreamRequestBody(MediaType contentType, InputStream inputStream) {
    if (inputStream == null) throw new NullPointerException("inputStream == null");
    this.contentType = contentType;
    this.inputStream = inputStream;
}

@Nullable
@Override
public MediaType contentType() {
    return contentType;
}

@Override
public long contentLength() throws IOException {
    return inputStream.available() == 0 ? -1 : inputStream.available();
}

@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
    Source source = null;
    try {
        source = Okio.source(inputStream);
        sink.writeAll(source);
    } finally {
        Util.closeQuietly(source);
    }
}

}

Based on your changes, @JakeWharton https://github.com/jakewharton, to
be changed to handle Uri:

public class InputStreamRequestBody extends RequestBody {
private final MediaType contentType;
private final ContentResolver contentResolver;
private final Uri uri;

public InputStreamRequestBody(MediaType contentType, ContentResolver contentResolver, Uri uri) {
    if (uri == null) throw new NullPointerException("uri == null");
    this.contentType = contentType;
    this.contentResolver = contentResolver;
    this.uri = uri;
}

@Nullable
@Override
public MediaType contentType() {
    return contentType;
}

@Override
public long contentLength() throws IOException {
    return -1;
}

@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
    sink.writeAll(Okio.source(contentResolver.openInputStream(uri)));
}

}

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/square/okhttp/issues/3585#issuecomment-327319196, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAEEEaaL1po9CilQcaTrQuBEKNofh3mzks5sfcgqgaJpZM4PNii9
.

@JakeWharton Based on your first comment, I guess I will close this because using TypeInputStream directly does not work very well with the current implementation of RequestBody.

@JakeWharton @jaredsburrows 3 years later. what's the best way to go from a android uri to okhttp "put"?

@neiljaywarner

val upload = POST("/updatePhoto",
    Field("id", uuid),
    Part("photo", Stream("image/*"), { "photo.jpg" }),
    Response<Boolean>())

val doUpload = okHttpClient.template(baseUrl, upload,
    blocking { body?.close(); isSuccessful })

doUpload(id) { contentResolver.openInputStream(uri) }

https://github.com/Miha-x64/Lychee#http

Was this page helpful?
0 / 5 - 0 ratings