1

I am getting an SSL exception when I try to upload a file to Amazon S3's pre-signed URL with OkHttp 3.9.1: SSLException: Write error: ssl=0xa0b73280: I/O error during system call, Connection reset by peer

It is the same problem as in another SO question but in my case it fails always. I upload just files over 1MiB in size, I have not tried small files.

As I mentioned in my answer in that question, switching to Java's HttpURLConnection fixed the problem and the upload works perfectly.

Here is my RequestBody implementation (in Kotlin) to upload a file from Android's Uri and I do use OkHttp's .put() method:

class UriRequestBody(private val file: Uri, private val contentResolver: ContentResolver, private val mediaType: MediaType = MediaType.parse("application/octet-stream")!!): RequestBody() {
    override fun contentLength(): Long = -1L

    override fun contentType(): MediaType? = mediaType

    override fun writeTo(sink: BufferedSink) {
        Okio.source((contentResolver.openInputStream(file))).use {
            sink.writeAll(it)
        }
    }
}

and here is my HttpURLConnection implementation:

private fun uploadFileRaw(file: Uri, uploadUrl: String, contentResolver: ContentResolver) : Int {
    val url = URL(uploadUrl)
    val connection = url.openConnection() as HttpURLConnection
    connection.doOutput = true
    connection.requestMethod = "PUT"
    val out = connection.outputStream

    contentResolver.openInputStream(file).use {
        it.copyTo(out)
    }

    out.close()
    return connection.responseCode
}

What is OkHttp doing differently so it can lead to this SSL exception?

EDIT:

Here is the OkHttp code to upload the file (using the default application/octet-stream mime type):

val s3UploadClient = OkHttpClient().newBuilder()
    .connectTimeout(30_000L, TimeUnit.MILLISECONDS)
    .readTimeout(30_000L, TimeUnit.MILLISECONDS)
    .writeTimeout(60_000L, TimeUnit.MILLISECONDS)
    .retryOnConnectionFailure(true)
    .build()

val body: RequestBody = UriRequestBody(file, contentResolver)
val request = Request.Builder()
        .url(uploadUrl)
        .put(body)
        .build()

s3UploadClient.newCall(request).execute()

And this is the JavaScript server code that generates the pre-signed upload URL:

const s3 = new aws.S3({
    region: 'us-west-2',
     signatureVersion: 'v4'
});
const signedUrlExpireSeconds = 60 * 5;

const signedUrl = s3.getSignedUrl('putObject', {
    Bucket: config.bucket.name,
    Key: `${fileName}`,
    Expires: signedUrlExpireSeconds
});
shelll
  • 3,234
  • 3
  • 33
  • 67

1 Answers1

3

This seems to work with retrofit library:

fun uploadImage(imagePath: String, directUrl: String): Boolean {
    Log.d(TAG, "Image: ${imagePath}, Url: $directUrl")
    val request = Request.Builder()
            .url(directUrl)
            .put(RequestBody.create(null, File(imagePath)))
            .build()
    val response = WebClient.getOkHttpClient().newCall(request).execute()
    return response.isSuccessful
}