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
});