检测SSL,从http切换到https的相关代码,基于Retrofit,kotlin
原来项目用的http接口,因为已经有人在使用了,所以要升级时,需要在同一个时间点切换
一个检查http接口是否通畅的类,基于rxjava
import com.google.gson.Gson import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction import io.reactivex.functions.Function import io.reactivex.schedulers.Schedulers import me.goldze.mvvmhabit.utils.KLog import me.goldze.mvvmhabit.utils.SPUtils import net.topstarts.base.base.TSBaseResponse import net.topstarts.base.config.TSConfigSetting import net.topstarts.base.okhttp.LogInterceptor import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET import retrofit2.http.Url import java.util.HashMap import java.util.concurrent.TimeUnit /** * 检查SSL是否可用 * 1.先检查 http 可用 则是http * 2. 如果http不可用, 再检查 https 可用才是 https * 3.如果 2 不可用,则重复 1 , 2 步骤 * */ class CheckSSLStateWorker() { private var retrofit: Retrofit? = null private val headers by lazy { HashMap<String, String>() } private val checkUrlBySSL by lazy { "https://xxxxxx" } private val checkUrlByHttp by lazy { "http://xxxx" } // 总共的最大测试次数 private val maxTestSSLNum = 3 // 现在测试的是第几次 private var testSSLNum = 0 init { buildNetWork() } val gson = Gson() /** * 这里是并发访问 */ /*override fun onHandleIntent(intent: Intent?) { val httpTestObservable = retrofit?.create(ApiService::class.java)?.checkIsSSL(checkUrlByHttp)?.subscribeOn(Schedulers.io()) ?.doOnNext { if (it.isOk) { it.msg = "http" } }?.retryWhen { it.zipWith(Observable.range(1, maxTestSSLNum), BiFunction<Throwable, Int, Int> { t1, t2 -> t2 }).flatMap(object : Function<Int, Observable<Long>> { override fun apply(retryCount: Int): Observable<Long> { KLog.i("------------- retryCount =$retryCount " + " thread ${Thread.currentThread().name}") return Observable.timer(5, TimeUnit.SECONDS) } }) }?.observeOn( AndroidSchedulers.mainThread() ) val sslTestObservable = retrofit?.create(ApiService::class.java)?.checkIsSSL(checkUrlBySSL)?.subscribeOn(Schedulers.io()) ?.doOnNext { if (it.isOk) { it.msg = "https" } }?.retryWhen { it.zipWith(Observable.range(1, maxTestSSLNum), BiFunction<Throwable, Int, Int> { t1, t2 -> t2 }).flatMap(object : Function<Int, Observable<Long>> { override fun apply(retryCount: Int): Observable<Long> { KLog.i("------------- retr0yCount =$retryCount " + " thread ${Thread.currentThread().name}") return Observable.timer(5, TimeUnit.SECONDS) } }) }?.observeOn( AndroidSchedulers.mainThread() ) var isNormal = false Observable.mergeDelayError(httpTestObservable, sslTestObservable).firstElement() .subscribe({ isNormal = true KLog.i("--------------> ${gson.toJson(it)}") showToastMsg(gson.toJson(it)) if (it.msg.equals("https")) { EventBus.getDefault().post("SSL://true") } else { EventBus.getDefault().post("SSL://false") } }, { it.printStackTrace() KLog.i("--------------> 出错") if (!isNormal) { EventBus.getDefault().post("SSL://error") } }, { if (!isNormal) { EventBus.getDefault().post("SSL://error") } KLog.i("--------------> 完成") }) }*/ // 默认 http 协议 val isHttpsByUser by lazy { SPUtils.getInstance().getBoolean( TSConfigSetting.APP_SETTING_CHANGE_HTTPS, false ) // true } /** * 这里是串行访问 * // 请求码 * @param resultCall 1 https 2 http 3 error * @param requestStr 请求码字符串 */ fun onHandleWork(resultCall: (Int, String) -> Unit, requestStr: String) { val httpTestObservable = retrofit?.create(ApiService::class.java)?.checkIsSSL(checkUrlByHttp)?.doOnNext { if (it.isOk) { KLog.i("-------------http doOnNext =$it ") it.msg = "http" } } val sslTestObservable = retrofit?.create(ApiService::class.java)?.checkIsSSL(checkUrlBySSL)?.doOnNext { if (it.isOk) { KLog.i("-------------https doOnNext =$it ") it.msg = "https" } } var isNormal = false // 依据默认的,确定先检查哪一个,以加快检查速度 val observableStart = if (isHttpsByUser) { Observable.concatArrayDelayError(sslTestObservable, httpTestObservable) } else { Observable.concatArrayDelayError(httpTestObservable, sslTestObservable) } observableStart.subscribeOn(Schedulers.io()).retryWhen { KLog.i("-------------retryWhen =${it} ") it.zipWith(Observable.range(1, maxTestSSLNum), BiFunction<Throwable, Int, Int> { t1, t2 -> t2 }).flatMap(object : Function<Int, Observable<Long>> { override fun apply(retryCount: Int): Observable<Long> { KLog.i("------------- retr0yCount =$retryCount " + " thread ${Thread.currentThread().name}") return Observable.timer(5, TimeUnit.SECONDS) } }) }.firstElement().observeOn( AndroidSchedulers.mainThread() ).subscribe({ isNormal = true KLog.i("--------------> ${gson.toJson(it)}") if (it.msg.equals("https")) { SPUtils.getInstance().put(TSConfigSetting.APP_SETTING_CHANGE_HTTPS, true) // eventBus.post("SSL://true::") resultCall.invoke(1, requestStr) } else { SPUtils.getInstance().put(TSConfigSetting.APP_SETTING_CHANGE_HTTPS, false) // eventBus.post("SSL://false::") resultCall.invoke(2, requestStr) } }, { // 默认 HTTP it.printStackTrace() KLog.i("--------------> 出错") if (!isNormal) { SPUtils.getInstance().put(TSConfigSetting.APP_SETTING_CHANGE_HTTPS, false) // eventBus.post("SSL://error::") resultCall.invoke(3, requestStr) } }, { if (!isNormal) { SPUtils.getInstance().put(TSConfigSetting.APP_SETTING_CHANGE_HTTPS, false) // eventBus.post("SSL://error::") resultCall.invoke(3, requestStr) } KLog.i("--------------> 完成") }) } private fun buildNetWork() { // headers.put("X-Tops-Src", "android") // headers.put("version", getVersionName(mContext)); // headers.put("Content-Type", "application/json;charset=UTF-8") val okHttpClient = OkHttpClient.Builder().addInterceptor(LogInterceptor()).connectTimeout( 20, TimeUnit.SECONDS ) //.readTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) // .callTimeout(60, TimeUnit.SECONDS) .build() retrofit = Retrofit.Builder().client(okHttpClient).addConverterFactory(GsonConverterFactory.create()) // 加上这句话 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).baseUrl("").build() } private interface ApiService { @GET abstract fun checkIsSSL( @Url url: String ): Observable<TSBaseResponse<Any>> } }
上面引用的 LogInterceptor 注意点:::后台可以把HTTP接口请求,从https返回
import android.text.TextUtils import com.google.gson.GsonBuilder import me.goldze.mvvmhabit.utils.KLog import me.goldze.mvvmhabit.utils.SingletionHolder import net.topstarts.module_base.BuildConfig import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response import okio.Buffer import java.net.URLDecoder import java.nio.charset.Charset import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit /** * Description: <r> * Author:Caosq<r> * Date:2019/10/29 15:07 */ class LogInterceptor : Interceptor { // 不对 HTML $ = 进行转义 val gson = GsonBuilder().disableHtmlEscaping().create() private val UTF8 = Charset.forName("UTF-8") // 为了打印JSON val headerMap = ConcurrentHashMap<String, String>() val bodyMap = ConcurrentHashMap<String, String>() override fun intercept(chain: Interceptor.Chain): Response { // 切换 https 或者 http 协议 val request = chain.request() val isNeddPrint = needPrint(request.url().toString()) if (!isNeddPrint) { KLog.i( "发送请求: method:${request.method()} url:${request.url()} 请求头:${gson.toJson( headerMap )}" ) val response = chain.proceed(request) //request= ${gson.toJson(request)}response= ${gson.toJson(response)} return response } val requestBody = request.body() var body: String? = null requestBody?.let { val buffer = Buffer() requestBody.writeTo(buffer) var charset: Charset? = UTF8 val contentType = requestBody.contentType() contentType?.let { charset = contentType.charset(UTF8) } body = buffer.readString(charset!!) } headerMap.clear() bodyMap.clear() //--------------- var bodyJson: String = "" if (!TextUtils.isEmpty(body)) { // val arrayStr = URLDecoder.decode(body, "UTF-8")?.split("&") val arrayStr = body?.split("&") // val arrayStr = body?.split("&") arrayStr?.forEach { val nameValue = it.split("=") try { //如果是文件流 这里会抛异常 bodyMap.put(nameValue[0], URLDecoder.decode(nameValue[1], "UTF-8")) } catch (e: Exception) { // 如果是文件流 可能没有KEY if (nameValue.size > 1) { bodyMap.put(nameValue[0], nameValue[1]) bodyJson = gson.toJson(bodyMap) } else { bodyJson = nameValue[0] // val type= object : TypeToken<Map<String?, String>>() {}.getType() // val map = gson.fromJson<Map<String, String>>(nameValue[0],type) // bodyMap.putAll(map) } } } } //------------------ request.headers().let { val names = it.names() for (name in names) { headerMap.put(name, it.get(name) ?: "") } } if (isNeddPrint) { val json = gson.toJson(bodyMap) KLog.i( "发送请求: method:${request.method()} url:${request.url()} 请求头:${gson.toJson( headerMap )} 请求参数: $bodyJson" ) } val startNs = System.nanoTime() val response = chain.proceed(request) val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) val responseBody = response.body() val rBody: String val source = responseBody!!.source() source.request(java.lang.Long.MAX_VALUE) val buffer = source.buffer() var charset: Charset? = UTF8 val contentType = responseBody.contentType() contentType?.let { try { charset = contentType.charset(UTF8) } catch (e: Exception) { KLog.e(e.message) } } rBody = buffer.clone().readString(charset!!) if (isNeddPrint) { //防止 body 为 null 如果砬到后台有将请求转发后,再返回时,会导致 response.request().url()与 开始的 request.url()不一样,但response.request().toString() /tag 里有相应的记录 if (request.url().equals(response.request().url())) { KLog.i( "收到响应: code: ${response.code()} 请求url:${response.request() .url()} Response: ${rBody} 花费时间: $tookMs 毫秒" ) } else { KLog.i( "收到响应: code: ${response.code()} 请求url:${response.request().url()} ${response.request() .toString()} Response: ${rBody} 花费时间: $tookMs 毫秒" ) } } return response } fun needPrint(currentUrl: String): Boolean { var needPrint = true noPrintUrl.forEach { if (currentUrl.contains(it)) { needPrint = false return needPrint } } return needPrint } // val noPrintUrl = arrayListOf<String>("platform/m/v1/banner", "platform/m/v1/permission","platform/m/v1/user/list", "oss/upload") // 文件上传 流下载时不能加打印会导致 System.err: java.net.ProtocolException: unexpected end of stream 字节流结束前后不一致 val noPrintUrl = arrayListOf<String>("platform/m/v1/banner", "oss/upload", "home/uaa/captcha/get") // val noPrintUrl = arrayListOf<String>("platform/m/v1/banner") // companion object : SingletionHolder<LogInterceptor, Boolean>(::LogInterceptor) }
检查SSL 是http 或者 https后,使用Retrofit拦截器 切换所有的APP接口,(这里是单例,还可以由用户手动切换)
import me.goldze.mvvmhabit.utils.KLog import me.goldze.mvvmhabit.utils.SPUtils import me.goldze.mvvmhabit.utils.SingletionHolder import net.topstarts.base.config.TSConfigSetting import net.topstarts.module_base.BuildConfig import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response /** * 切换 ssl的 拦截 */ class ChangeSSLInterceptor private constructor(private val isHttps: Boolean) : Interceptor { var isHttpsByUser: Boolean = isHttps // var isSelByUser: Boolean = SPUtils.getInstance().getBoolean(TSConfigSetting.APP_SETTING_CHANGE_HTTPS_By_USER, false) override fun intercept(chain: Interceptor.Chain): Response { // 切换 https 或者 http 协议 val request = changeHttpsOrHttpAgreementt(chain.request()) return chain.proceed(request) } /** * 切换 https 或者 http 协议 */ private fun changeHttpsOrHttpAgreementt(request: Request): Request { var url = request.url().toString() if (BuildConfig.FLAVOR.equals("dev_rep")) {//内网还是 http val newBuilder = request.newBuilder() return newBuilder.url(url).build() } if (url.contains("oss/upload")) { // 上传图片地址 固定为 https 下载图片,固定为 http url = "${BuildConfig.fileDomain}oss/upload" val newBuilder = request.newBuilder() return newBuilder.url(url).build() } // KLog.i("-------------------->>isHttpsByUser= $isHttpsByUser $url") if (isHttpsByUser) { // 是 HTTPS if (!url.contains("https://")) { url = url.replace("http://", "https://") } else { return request } } else { //是http if (url.contains("https://")) { url = url.replace("https://", "http://") } else { return request } } val newBuilder = request.newBuilder() return newBuilder.url(url).build() } /** * @param isUserSel true是用户手动选择,一旦用户手动选择了,就不再使用SSL检查的了 */ fun changeHttpsByUser(isHttps: Boolean, isUserSel: Boolean = false) { if (isSelByUser || isUserSel) { if (!isUserSel) { return } isHttpsByUser = isHttps if (!isSelByUser) { isSelByUser = true } } else { isHttpsByUser = isHttps if (isUserSel) { isSelByUser = true } } } /** * 重新设成代码控投制 */ fun resetSelByUserToFalse() { isSelByUser = false } companion object : SingletionHolder<ChangeSSLInterceptor, Boolean>(::ChangeSSLInterceptor) }