请求参数加密and响应报文解密
为了数据安全,大多数请求和响应报文都是AES加密的,因此我们也需要Retrofit帮我们完成这部分逻辑,实际上这部分逻辑依然是交给Okhttp完成。和添加公参的过程类似,我们依然是新增拦截器,只不过这次还需要对响应报文进行解密处理。
依然是废话不多说,看代码!
/** * 加解密拦截器基类 */ abstract class BaseEncryptAndDecryptInterceptor : Interceptor{ final override fun intercept(chain: Interceptor.Chain): Response { val originRequest = chain.request() return decryptResponse(chain.proceed( originRequest.body.run { //如果body为空,则直接往下传 if (this == null) { originRequest } //如果body不为空,则解密 else { originRequest.newBuilder().apply { method( originRequest.method, encrypt(this@run) ) }.build() } } )) } //加密参数 @Throws(IOException::class) abstract fun encrypt(requestBody: RequestBody): RequestBody //解密响应报文 @Throws(IOException::class) abstract fun decryptResponse(response: Response): Response }
使用起来也非常简单,只要实现了那两个抽象方法即可,RequestBody和Response都有一个类似克隆的方法,以便于返回一个全新的对象供修改。以下是笔者的实现(省略了部分代码,主要是具体加密部分,你可以替代为你们公司的密钥工具)。
override fun encrypt(requestBody: RequestBody): RequestBody { return requestBody.string()//1.转成String .encrypt()//2.加密 .toRequestBody(requestBody.contentType())//3.转成requestBody,保留原来的contentType } override fun decryptResponse(response: Response): Response { //如果不成功则不尝试解密 if (!response.isSuccessful) { return response } val responseBody = response.body ?: return response return response.newBuilder() .body(responseBody.encrypt()) .build() }
讲了2个关于okhttp的拦截器的逻辑定制的做法,想必聪明的读者已经可以举一反三了,利用okhttp的拦截器我们可以实现很多客制化逻辑,例如重试逻辑等,发挥你的想象力,动手尝试吧!等等,好像我们还没有来到Retrofit层面呢。
别急,下面即将带领你进入Retrofit的自定义注解+自定义注解逻辑的世界!
Retrofit自定义注解
Retrofit给我们提供了好多有用的注解,但是Retrofit却没有给我们增加自定义注解的机会,所有Retrofit预设的注解的解析过程都是硬编码在一堆if-else语句里面的,也就是说:Retrofit并没有在这个层面增加类似CallAdapter的扩展性。
那我们该怎么办呢?实则Retrofit官方早就已经想到了这一层了,于是在Retrofit构建OKhttp的Request的过程中,通过给Request新增一个tag,把方法通过tag传到这个Request上面,具体我们可以看源码,具体在Retrofit源码的RequestFactory.java中。
okhttp3.Request create(Object[] args) throws IOException { //...省略部分源码 RequestBuilder requestBuilder = new RequestBuilder( httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); //...省略部分源码 //新增一个tag,把方法添加到tag里面去 return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build(); }
既然Retrofit给Request添加了一个tag,tag中包含了当前的方法,那么我们就可以通过解析request来拿到我们想要的注解,然后通过注解本身的信息,来完成一些逻辑定制了。
同样的,让我们重新回到okhttp的拦截器中去,直接看代码!
/** * 返回某个Retrofit定义在方法上的注解,例如[POST],[GET] */ fun <T : Annotation> Request.getMethodAnnotation(annotationClass: Class<T>): T? { return tag(Invocation::class.java)?.method()?.getAnnotation(annotationClass) }
可恶,代码居然如此简单,实际上就是通过类型来找到某个tag,再通过tag来找到我们定义的方法,紧接着找到方法上面的注解即可!
为了讲明白如何定制逻辑,我们先假设一个场景,我们有一部分的接口是需要给url后面接一个参数,例如?version=2,那么我们应该怎么做呢?只需要三步即可:
- 新增一个注解,包含一个属性versionCode
/** * 用于标记retrofit接口方法,声明当前请求的new_versioncode的值 */ @Target(AnnotationTarget.FUNCTION) annotation class VersionCode( //版本号, val versionCode: Int, )
- 在Retrofit方法中使用该注解。
@POST("/friend/list") @VersionCode(versionCode = 2) suspend fun requestFriend( @Body friendRequestParam: FriendRequestParam ): NetworkResult<FriendBean>
- 在拦截器中获取到该注解,并修改Request
/** * 后台加密版本号控制,主要修改new_versioncode字段的值 */ class VersionControlInterceptor @Inject constructor() : Interceptor { companion object { //需要添加的前缀 private const val VERSION_CODE_PREFIX = "?versioncode=" } override fun intercept(chain: Interceptor.Chain): Response { val originRequest = chain.request() val annotation = originRequest.getMethodAnnotation(VersionCode::class.java) return chain.proceed( //没加注解,跳过处理 if (annotation == null) originRequest //在原url中拼接?new_versioncode=x字符串 else originRequest.newBuilder() .url("${originRequest.url}$VERSION_CODE_PREFIX${annotation.versionCode}") .build() ) } }
大功告成!我们通过查找request中的tag的方式找到原始的method,然后再通过method找到了我们需要的注解,这样就打通了Retrofit和okhttp在定制逻辑上的关系了,通过在接口方法上增加注解的方式,让逻辑定制更加直观!
很感谢你看到这里,这篇已经结束了,下一篇我将继续讲讲Retrofit的一些全局操作。