原来项目用的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: <\br> * Author:Caosq<\br> * 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()}\nurl:${request.url()}\n请求头:${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()}\nurl:${request.url()}\n请求头:${gson.toJson( headerMap )}\n请求参数: $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()}\n请求url:${response.request() .url()}\nResponse: ${rBody} 花费时间: $tookMs 毫秒" ) } else { KLog.i( "收到响应: code: ${response.code()}\n请求url:${response.request().url()}\n${response.request() .toString()}\nResponse: ${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) }