liangjingkanji / Net

Android 基于协程/OkHttp网络请求工具
http://liangjingkanji.github.io/Net/
MIT License
1.8k stars 209 forks source link

使用文件流上传文件 #189

Closed liangjingkanji closed 11 months ago

liangjingkanji commented 11 months ago

关于此问题, 我修改了很多次, 但这由框架来实现可能会引发超出预期的问题发生

官方对此也有讨论 square/okhttp#3585

  1. 重复读取输入流导致错误
  2. 零次读取请求体导致输入流内存泄露

建议是将InputStream先写入到File中, 或者使用Uri.toRequestBody替代

liangjingkanji commented 11 months ago
/**
 * 创建输入流的ResponseBody
 *
 * @param contentLength 如果不指定长度默认将使用[InputStream.available]作为长度
 *
 * https://github.com/square/okhttp/issues/3585
 * 1. 在需要重复读取输入流时会导致错误
 * 2. 零次读取请求体导致输入流内存泄露
 *
 * 建议使用以下方法替代
 * @see File.toRequestBody
 * @see Uri.toRequestBody
 *
 * 如果重复读取抛出[IllegalStateException]
 */
@Throws(IllegalStateException::class)
fun InputStream.toRequestBody(
    contentType: MediaType? = MediaConst.OCTET_STREAM,
    contentLength: Long? = null
): RequestBody {
    return object : RequestBody() {
        override fun contentType() = contentType

        val availableLength: Long by lazy {
            if (contentLength != null) return@lazy contentLength
            // 如果输入流字节过长超过Int.MAX_VALUE会无法获取到正确内容长度
            val availableLength = available()
            if (availableLength == 0) -1L else availableLength.toLong()
        }

        override fun contentLength(): Long {
            return availableLength
        }

        override fun writeTo(sink: BufferedSink) {
            source().use { source ->
                sink.writeAll(source)
            }
        }

        override fun isOneShot(): Boolean {
            // 由于输入流只允许读取一次, 所以禁止请求重试
            return true
        }
    }
}