强制OkHttp3客户端保持TLS连接打开

时间:2018-07-31 13:42:40

标签: android ssl retrofit2 tls1.2 okhttp3

我正在开发一个应用程序,必须在其中通过HTTPS与具有自签名证书的嵌入式IoT产品进行通信。我成功设置了OkHttp以使用自签名证书,并且正在使用RxCallAdapters通过Retrofit2进行网络调用。

嵌入式产品一次只能处理一个连接,因此我将底层的OkHttp实例配置为仅允许一个连接(据我所知,也许有更好的方法)。

如果我仅发出GET请求,则握手成功完成,并且整个请求序列的连接均保持打开状态。嵌入式产品在闲置5秒钟后会关闭连接,因此预计我必须每隔一段时间重做一次握手。

开始执行PUT和POST请求时,问题开始出现。当请求流从一种请求类型更改为另一种请求类型时,或者实际上在任何请求是PUT或POST时,OkHttp似乎都不会保持现有连接的打开状态。例如:

手握手-获取-获取-获取-手握手-放置-手握手-获取...等

如何强制OkHttp保持不同类型的请求之间的连接打开?我知道这无关紧要,但似乎与响应代码有关。嵌入式设备上的REST API为GET提供200,为POST提供201,为PUT提供204。

这是我用来配置OkHttp和改造实例的相关代码:

@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
    val httpLoggingInterceptor = HttpLoggingInterceptor { message ->       Timber.tag("OkHttp").d(message) }

    setLogLevel(httpLoggingInterceptor)

    return httpLoggingInterceptor
}

private fun setLogLevel(httpLoggingInterceptor: HttpLoggingInterceptor) {
    if (BuildConfig.DEBUG) {
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
    } else {
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
    }
}

@Provides
@Singleton
fun provideContentTypeHeaderInterceptor(): Interceptor {
    return Interceptor { chain ->
        val originalRequest = chain.request()

        val requestBuilder = originalRequest.newBuilder()
        requestBuilder.header("Content-Type", "application/json")
        chain.proceed(requestBuilder.build())
    }
}

@Provides
@Singleton
@LocalOkHttpConnectionPool
fun provideLocalConnectionPool() = ConnectionPool(1, 5, TimeUnit.SECONDS)


@Provides
@Singleton
@LocalOkHttpClient
fun provideLocalOkHttpClient(headerInterceptor: Interceptor,
                         httpLoggingInterceptor: HttpLoggingInterceptor,
                         @LocalOkHttpConnectionPool connectionPool: ConnectionPool,
                         context: Context): OkHttpClient {

    val cf = CertificateFactory.getInstance("X.509")
    val cert = context.getResources().openRawResource(R.raw.ca) // Place your 'my_cert.crt' file in `res/raw`

    val ca = cf.generateCertificate(cert)
    cert.close()

    val keyStoreType = KeyStore.getDefaultType()
    val keyStore = KeyStore.getInstance(keyStoreType)
    keyStore.load(null, null)
    keyStore.setCertificateEntry("ca", ca)

    val tmfs = CompositeX509TrustManager.getTrustManagers(keyStore)

    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(null, tmfs, null)

    val hostNameVerifier = HostnameVerifier { hostname, session ->
        return@HostnameVerifier true
    }

    val connectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .allEnabledCipherSuites()
        .allEnabledTlsVersions()
        .supportsTlsExtensions(true)
        .build()

    val connectionSpecs = mutableListOf(connectionSpec)

    val okHttpClient = OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .hostnameVerifier(hostNameVerifier)
        .addInterceptor(headerInterceptor)
        .addInterceptor(httpLoggingInterceptor)
        .connectionPool(connectionPool)
        .sslSocketFactory(sslContext.socketFactory, tmfs.first() as X509TrustManager)
        .connectTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .build()

    return  okHttpClient
}

@Provides
@Singleton
@LocalRetrofit
fun provideLocalRetrofit(@LocalOkHttpClient okHttpClient: OkHttpClient,
                         gson: Gson): Retrofit {
    return Retrofit.Builder().baseUrl(NetworkUtils.BASE_AUTH_URL)
            .addConverterFactory(NullOnEmptyConverterFactory())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build()
}

0 个答案:

没有答案