检测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)
}
原文地址:https://www.cnblogs.com/caosq/p/13273770.html