Introduction:

Why did you choose such a topic for discussion? It’s been a long time since Square opened source these two excellent web libraries, and there are plenty of source code parsing and tramping guides everywhere. The starting point for this article is: Start with a few interesting field questions, dig into Retrofit and OkHttp’s design philosophy, and pick some code to read. By the end of this article, you’ll be familiar with the details of Retrofit and OkHttp


2020-09-02 21:19:48

Considering writing such a long article, and a lot of code, reading will be very tired, and added Demo github.com/leobert-lan… ; If you think these contents are good, please give the article and project a thumbs up and support, bitter.


How do I set an independent timeout for requests

Okhttp provides several apis for setting the timeout, such as:

  • callTimeout
  • connectTimeout
  • readTimeout
  • writeTimeout

The last three strictly comply with the meaning of timeout in HTTP protocol. CallTimeout is a global timeout period provided by OKHTTP to the client program. An HTTP or HTTPS request starts from DNS resolution, to the connection, to the sending body, to the server response, to the server return data. And possible redirects, the entire process needs to be completed within this time, otherwise the client will voluntarily give up or disconnect even if the timeout expires. Generally, we don’t want to specify this time dynamically (by default okHTTP is infinite), so let’s look at the last three first. The specific meanings of the three timeouts are briefly described below:

  • If the connection times out, the TCP connection is established according to the three-way handshake (SSL or TLS is put aside) after the destination IP is obtained (if the IP is directly specified, then DNS resolution is not required). If the connection connection times out, then gg is established.
  • We know that the packet is encapsulated into a data frame and sent. After receiving the packet, the receiver will receive an ACK. This write timeout period is for each frame.
  • Read timeout, similar to write timeout, is when the server sends data to itself (client).

We need to set flexible timeouts for different levels of services because the importance of services varies and the complexity of services processed by the server varies.

In the past, we might have seen a less responsible practice of creating multiple okHTTP Client instances and retrofitClient instances with different timeouts, which is not much to comment on.

Looking through the code, we found that retroFit’s facets design allows us to set these three times individually for each request:

interface Chain { fun request(): Request fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain // irrelevant code omitted}Copy the code

The method, of course, is known as interceptors:

class OverrideTimeoutInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() var newChain = chain originalRequest.header(Consts.OVERRIDE_CONNECT_TIMEOUT)? .toIntOrNull()? .let { if (it > 0) newChain = newChain.withConnectTimeout(it, TimeUnit.MILLISECONDS) } originalRequest.header(Consts.OVERRIDE_WRITE_TIMEOUT)? .toIntOrNull()? .let { if (it > 0) newChain = newChain.withWriteTimeout(it, TimeUnit.MILLISECONDS) } originalRequest.header(Consts.OVERRIDE_READ_TIMEOUT)? .toIntOrNull()? .let { if (it > 0) newChain = newChain.withReadTimeout(it, TimeUnit.MILLISECONDS) } return newChain.proceed(originalRequest) } }Copy the code

And then Retrofit works in a way that we’re familiar with, creating dynamic proxies by reflection, knowing exactly what our network interfaces are, and providing interceptors that allow us to “interfere” in the process of creating executable objects, so we can use this mechanism directly. The timeout Settings are read by Retrofit. From the above we can see that we put the information in the Header, but if there is no special requirement, we don’t have to remove it after reading. If we put it in a QueryString or Field, we still need to remove it to avoid problems.

We’ll take a look at the source code, down the invocation chain class is okhttp3 internal. HTTP. RealInterceptorChain set the timeout was eventually applied in all those places:

  • okhttp3.internal.connection.RealConnection#newCodec
  • okhttp3.internal.connection.ExchangeFinder
  • okhttp3.internal.http2.Http2ExchangeCodec#writeRequestHeaders

As for the CallTimeout mentioned above, if we are determined to do something else, will the source code give us this opportunity? Actually, this problem comes from the discussion of an issue in the Okhttp project THAT I participated in: links.

The design implementation of this global timeout may vary between Okhttp versions. In version 4.3.1, it looks like this:

class Transmitter( private val client: OkHttpClient, private val call: Call ) { private val connectionPool: RealConnectionPool = client.connectionPool.delegate private val eventListener: EventListener = client.eventListenerFactory.create(call) private val timeout = object : AsyncTimeout() { override fun timedOut() { cancel() } }.apply { timeout(client.callTimeoutMillis.toLong(), MILLISECONDS) } //... Extraneous code omitted}Copy the code

The controller that controls timeout is depended on by RealCall:

internal class RealCall private constructor( val client: OkHttpClient, /** The application's original request unadulterated by redirects or auth headers. */ val originalRequest: Request, val forWebSocket: Boolean ) : Call { /** * There is a cycle between the [Call] and [Transmitter] that makes this awkward. * This is set after immediately after creating the call instance. */ private lateinit var transmitter: Transmitter //... Extraneous code omitted}Copy the code

This RealCall is the class in Okhttp that describes the request.

Reading the code, we can see that okHTTP doesn’t give us the opportunity to modify RealCall directly in the code. Even the creation of instances of this class object is relatively closed. Even if we inherit OkhttpClient and reproduce the implementation factory, we can only make minor changes to the Request without making major changes:

static class OkHttpClient2 extends OkHttpClient { public OkHttpClient2(@NotNull Builder builder) { super(builder); } // Prepares the [request] to be executed at some point in the future. // // override fun newCall(request: Request): Call { // return RealCall.newRealCall(this, request, ForWebSocket = false) @override public Call newCall(@override Request) { RealCall Return super.newCall(request); RealCall return super.newCall(request); } static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) { return new OkHttpClient2(client.newBuilder()); }}Copy the code

Ultimately, we concluded that OkHttp did not provide a normal way for us to “dynamically” change this global timeout, perhaps contrary to their design.

Square’s Jesse Wilson also gave an official explanation

Yep. We don’t have a mechanism to adjust a timeout that’s already started. By the time the interceptor runs we’re already subject to the call timeout.

There is no mechanism to dynamically adjust the timing after it has started. And must have entered the interceptor logic, in fact the timing has already started.

This can be concluded directly from the RealCall code:

override fun execute(): Response { synchronized(this) { check(! executed) { "Already Executed" } executed = true } transmitter.timeoutEnter() transmitter.callStart() try { client.dispatcher.executed(this) return getResponseWithInterceptorChain() } finally { client.dispatcher.finished(this) }  } override fun enqueue(responseCallback: Callback) { synchronized(this) { check(! executed) { "Already Executed" } executed = true } transmitter.callStart() client.dispatcher.enqueue(AsyncCall(responseCallback)) }Copy the code

So, if you have to change it, either change the source code for Okhttp or, as I said in the issue discussion, use reflection.

2020-10-28 09:18:20:

I re-read some of the code and the contents of the issue. There was some misunderstanding earlier, okHTTP does not provide a mechanism to modify the “total timeout” of a request that has already been processed, because it is calculated from the beginning.

A request that has not yet been processed can be dynamically modified. The API exposes the way to modify the call timeout. We just need to get the Call object and the timeout duration configured for the call before the request is processed. There are two safer approaches:

CallAdapter mechanism that can handle annotations directly, even custom annotations

OkhttpClient generates call phase processing, requiring timeout to be written to something like a header

Judgment basis for network layer optimization – more sophisticated statistical tools

How to create a Request in Okhttp, how to create a RealCall, how to use Chain to describe aspects and responsibilities, encapsulate Response, and how to manage requests.

At this time, we encountered a new problem: the business line needed to optimize the network layer from the technical level, and we needed a mechanism to collect: how many events were spent on the request, and how much time was spent on its own business processing; The server also needs to capture the time taken by the business response.

We therefore need a mechanism to collect critical information about the time spent at the network layer.

We can imagine the following steps from the user triggering a user action to the interface returning data that is ultimately fed back to the user at the UI level:

  • Pre-service: user input and scenario verification
  • Creating an API request
  • Switch to the worker thread to execute the API request
  • The parse API returns the results and switches to the UI thread to feed back the results to the user

Here, we basically think that the pre-service has nothing to do with the network layer, nor is it a time-consuming business. We assume that it does not need to be optimized for time, nor does it do time collection.

We don’t need to be too harsh on creating API requests if we ignore the time spent on reflection and consider Retrofit to be irreplaceable;

Switch the worker thread and execute the API request, which may encounter a thread pool wait, or wait for the maximum number of domain or IP connections to be reached. We also need to be concerned about the time it takes to resolve DNS, create a connection, and verify TLS handshake.

To return data parsing and result feedback, we need to consider whether json data (probably not XML) takes too much time, and take a hard look at whether the main thread takes too much time (such as writing data to disk for caching, the main thread does a lot of object creation and attribute copy, etc.).

Let’s first look at the parts directly related to network requests

In the previous problem, we simply touched on a class called Transmitter and found that it holds an object dependency of a class called EventListener. Take a look directly at the source code, which is quite long and I have deleted all the comments:

abstract class EventListener { open fun callStart(call: Call) { } open fun proxySelectStart(call: Call,url: HttpUrl) { } open fun proxySelectEnd(call: Call,url: HttpUrl,proxies: List<@JvmSuppressWildcards Proxy>) { } open fun dnsStart( call: Call, domainName: String ) { } open fun dnsEnd( call: Call, domainName: String, inetAddressList: List<@JvmSuppressWildcards InetAddress> ) { } open fun connectStart( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy ) { } open fun secureConnectStart( call: Call ) { } open fun secureConnectEnd( call: Call, handshake: Handshake? ) { } open fun connectEnd( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol? ) { } open fun connectFailed( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol? , ioe: IOException ) { } open fun connectionAcquired( call: Call, connection: Connection ) { } open fun connectionReleased( call: Call, connection: Connection ) { } open fun requestHeadersStart( call: Call ) { } open fun requestHeadersEnd(call: Call, request: Request) { } open fun requestBodyStart( call: Call ) { } open fun requestBodyEnd( call: Call, byteCount: Long ) { } open fun requestFailed( call: Call, ioe: IOException ) { } open fun responseHeadersStart( call: Call ) { } open fun responseHeadersEnd( call: Call, response: Response ) { } open fun responseBodyStart( call: Call ) { } open fun responseBodyEnd( call: Call, byteCount: Long ) { } open fun responseFailed( call: Call, ioe: IOException ) { } open fun callEnd( call: Call ) { } open fun callFailed( call: Call, ioe: IOException ) { } interface Factory { fun create(call: Call): EventListener } companion object { @JvmField val NONE: EventListener = object : EventListener() { } } }Copy the code

In this case, we’ll focus on two times: when the EventListener object was created and when the callStart method was called.

We also have the impression that when the Transmitter object is created, the EventListener object is created through the factory:

class Transmitter(private val client: OkHttpClient,private val call: Call) {
  private val eventListener: EventListener = client.eventListenerFactory.create(call)
  //...
}
Copy the code

CallStart of EventListener is called when there is no Transmitter#callStart:

fun callStart() {
  this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
  eventListener.callStart(call)
}
Copy the code

RealCall is called immediately when execute() or enqueue() is called, so the thread pool wait time and connection upper limit wait time only occur after callStart, while DNS resolution wait time can be calculated from EventListener.

As for Transmitter, it is created at the same time when RealCall is created. If there is no “accident”, the creation time of EventListener and callStart should be very close, because Retrofit only does some simple pre-service. Execute or enQueue is called immediately after the RealCall is created; If the time difference is large, it is worth analyzing whether there is a “magic change” to achieve the business.

To add a custom EventListener for the request, simply implement the factory and register with the API:

okhttp3.OkHttpClient.Builder#eventListenerFactory()
Copy the code

Assuming we have implemented a detailed EventListener, moving on, assuming all goes well and we get the data back from the server, how does the business process event work elegantly? Let’s briefly look at three scenarios

  • Just use Retrofit’s Callback mechanism, which is nothing to talk about, starting from the base class
  • With RxJava, you can start with Adapter, start with Observable
  • Using a Kotlin coroutine (supported internally by Retrofit), you can cut in from Flow

Here I provide the processing situation of using RxJava in our project, rewriting the Adapter provided by Square, just for reference: Note RetrofitClient.net Tracker? ApiRequestInfoTrack (call. The request ())

internal class CallEnqueueObservable<T>(private val originalCall: Call<T>) : Observable<Response<T>? >() { override fun subscribeActual(observer: Observer<in Response<T>? >) { // Since Call is a one-shot type, clone it for each new observer. val call = originalCall.clone() val callback = CallCallback(call, observer) observer.onSubscribe(callback) if (! callback.isDisposed) { call.enqueue(callback) } } private class CallCallback<T> internal constructor(private val call: Call<*>, private val observer: Observer<in Response<T>? >) : Disposable, Callback<T> { @Volatile private var disposed = false var terminated = false override fun onResponse(call: Call<T>, response: Response<T>) { if (disposed) return try { val body = response.body() if (body is BaseData) { body.callHolder = call } observer.onNext(response) if (! disposed) { terminated = true observer.onComplete() } } catch (t: Throwable) { if (terminated) { RxJavaPlugins.onError(t) } else if (! disposed) { try { observer.onError(t) } catch (inner: Throwable) { Exceptions.throwIfFatal(inner) RxJavaPlugins.onError(CompositeException(t, inner)) } } } try { RetrofitClient.netTracker?.apiRequestInfoTrack(call.request()) } catch (e: Exception) { L.e(e) } } override fun onFailure(call: Call<T>, t: Throwable) { if (call.isCanceled) return try { observer.onError(t) } catch (inner: Throwable) { Exceptions.throwIfFatal(inner) RxJavaPlugins.onError(CompositeException(t, inner)) } } override fun dispose() { disposed = true call.cancel() } override fun isDisposed(): Boolean { return disposed } } } internal class CallExecuteObservable<T>(private val originalCall: Call<T>) : Observable<Response<T>?>() { override fun subscribeActual(observer: Observer<in Response<T>?>) { // Since Call is a one-shot type, clone it for each new observer. val call = originalCall.clone() val disposable = CallDisposable(call) observer.onSubscribe(disposable) if (disposable.isDisposed) { return } var terminated = false try { val response = call.execute() if (! disposable.isDisposed) { val body = response.body() if (body is BaseData) { body.callHolder = originalCall // if the originalCall is not used, the internal call and request will not be created. // if the originalCall is not used, the internal call and request will be created. } observer.onNext(response)} if (! disposable.isDisposed) { terminated = true observer.onComplete() } } catch (t: Throwable) { Exceptions.throwIfFatal(t) if (terminated) { RxJavaPlugins.onError(t) } else if (! disposable.isDisposed) { try { observer.onError(t) } catch (inner: Throwable) { Exceptions.throwIfFatal(inner) RxJavaPlugins.onError(CompositeException(t, inner)) } } } try { RetrofitClient.netTracker?.apiRequestInfoTrack(call.request()) } catch (e: Exception) { L.e(e) } } private class CallDisposable internal constructor(private val call: Call<*>) : Disposable { @Volatile private var disposed = false override fun dispose() { disposed = true call.cancel() } override fun isDisposed(): Boolean { return disposed } } }Copy the code

Ok, at this point we thought about a question: how can we combine the business-time-consuming collection and request start times that we have been forced to separate for practical purposes? To put it bluntly, how do I know the request start time at execution time? RetrofitClient.netTracker?.apiRequestInfoTrack(call.request())

This again begins to test our familiarity with the source code. We’ve all used Square’s log blocker for Retrofit, wondering why it prints a prototype of the called method:

[Method:get] (url),tag=[package.UserApi#getUserInfo(String:uid)]
Copy the code

This is an okHTTP Request. Let’s look at this class:

class Request internal constructor( @get:JvmName("url") val url: HttpUrl, @get:JvmName("method") val method: String, @get:JvmName("headers") val headers: Headers, @get:JvmName("body") val body: RequestBody? , internal val tags: Map<Class<*>, Any> ) { private var lazyCacheControl: CacheControl? = null val isHttps: Boolean get() = url.isHttps fun header(name: String): String? = headers[name] fun headers(name: String): List<String> = headers.values(name) /** * Returns the tag attached with `Object.class` as a key, Or null if no tag is attached with * that key. * * Prior to OkHttp 3.11, this method never returned null if no tag was attached. Instead it * returned either this request, or the request upon which this request was derived with * [newBuilder]. */ fun tag(): Any? = tag(Any::class.java) fun <T> tag(type: Class<out T>): T? = type.cast(tags[type]) //... Override fun toString() = buildString {Request{method=") append(method) append(", url=") append(url) if (headers.size ! = 0) { append(", headers=[") headers.forEachIndexed { index, (name, value) -> if (index > 0) { append(", ") } append(name) append(':') append(value) } append(']') } if (tags.isNotEmpty()) { append(", tags=") append(tags) } append('}') }Copy the code

That’s right, the Request that created the RealCall, it can set some tags, and Retrofit created the OK Request with tag information, see: Retrofit2. RequestFactory#create(Object[] args), added a Invocation tag:

public final class Invocation { public static Invocation of(Method method, List<? > arguments) { checkNotNull(method, "method == null"); checkNotNull(arguments, "arguments == null"); return new Invocation(method, new ArrayList<>(arguments)); // Defensive copy. } private final Method method; private final List<? > arguments; /** Trusted constructor assumes ownership of {@code arguments}. */ Invocation(Method method, List<? > arguments) { this.method = method; this.arguments = Collections.unmodifiableList(arguments); } public Method method() { return method; } public List<? > arguments() { return arguments; } @Override public String toString() { return String.format("%s.%s() %s", method.getDeclaringClass().getName(), method.getName(), arguments); }}Copy the code

Unfortunately, Retrofit’s RequestFactory is Final and not implemented as an interface, so we can’t add the required tags directly from Retrofit. Recall that we mentioned okHTTP’s RealCall creation above. OkHttpClient implements this Factory.

static class OkHttpClient2 extends OkHttpClient { public OkHttpClient2(@NotNull Builder builder) { super(builder); } @notnull @override public Call newCall(@notnull Request Request) { // The bottom layer is basically retrofit Proxy mode created Call, the actual use of RequestFactory to create request, then create OK Call. / / that out of the tag should be null NetLoggingEventListener. RetrofitRequest tag = null; try { tag = request.tag(NetLoggingEventListener.RetrofitRequest.class); } catch (Exception e) { L.e(e); } if (tag == null) request = request.newBuilder().tag(NetLoggingEventListener.RetrofitRequest.class, new NetLoggingEventListener.RetrofitRequest(System.currentTimeMillis())).build(); return super.newCall(request); } static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) { return new OkHttpClient2(client.newBuilder()); }}Copy the code
class RetrofitRequest(val timeMillis: Long) {
    override fun toString(): String {
        return "RetrofitRequest(timeMillis=$timeMillis)"
    }
}
Copy the code

We define a class that describes the information we need now and may need later and write it as a tag in the OKHTTP Request. Fun

tag(type: Class

): T? = type.cast(tags[type])API to fetch it.

In fact, see here, you can already complete such a time collection tool

I also want to add a detail that I didn’t pay attention to at that time:

As you can see from the code, Retrofit also defines the Call interface and the OkHttpCall implementation class. The OkHttpCall implementation class can be thought of as following the Facade pattern and the Proxy pattern. It does not create OkHttp calls without actually using them for requests. Key code created:

private okhttp3.Call createRawCall() throws IOException {
  okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}
Copy the code

This method is called in only three places: get the Request instance, execute, and enQueue; The last two are real requests. First, if called before the request, it is usually intended to make some modifications.

CallExecuteObservable (RxJava), CallExecuteObservable (), CallExecuteObservable ()

 // Since Call is a one-shot type, clone it for each new observer.
  val call = originalCall.clone()
  val disposable = CallDisposable(call)
Copy the code

To avoid retries and reuse problems in stream processing, we clone originalCall. Therefore, we must use the clone call object to fetch Request and tag. Otherwise, we will encounter a problem when using originalCall. Request is created when it is actually used, and does not correspond to the Request start time 😂.

Full stack support for man-machine verification, Square provides us with RxJava& coroutine Adapter discarded

This time, we encountered a new problem: some interfaces were accessed too frequently by a single IP address, which may be malicious behavior. Man-machine verification link needs to be added, and it is required to re-send the original request after verification.

After some discussion with the server, we decided to use a unified error code to express the need for man-machine verification.

In fact, this problem looks very clear. It is found that a specific error code is responded, that is, man-machine verification is popped up, the callback is registered, and relevant parameters are obtained after completion, which are added to the original Request and re-initiated.

Instead of going directly to the Observable as we did in the previous question, we do this in the base class of the request’s response callback. Of course, if you are using an official Adapter, there is no way to handle the Observable directly.

Let’s put that aside for the moment and get back to the title, what does the RxJava& Coroutine Adapter Square gives us throw away?

According to what we know about RxJava and coroutine Flow, both are Response based subscription processing frameworks, and the source of the flow is actually Okhttp’s Response, or more literally, the object that parses the body of the Response into. Because of the nature of the business, most of the time we only need to care about the result returned, not the request itself. Let’s look at this code again:

internal class CallExecuteObservable<T>(private val originalCall: Call<T>) : Observable<Response<T>? >() { override fun subscribeActual(observer: Observer<in Response<T>? >) { // Since Call is a one-shot type, clone it for each new observer. val call = originalCall.clone() val disposable = CallDisposable(call) observer.onSubscribe(disposable) if (disposable.isDisposed) { return } var terminated = false try { val response = call.execute() if (! disposable.isDisposed) { val body = response.body() if (body is BaseData) { body.callHolder = originalCall // if the originalCall is not used, the internal call and request will not be created. // if the originalCall is not used, the internal call and request will be created. } observer.onNext(response)} if (! disposable.isDisposed) { terminated = true observer.onComplete() } } catch (t: Throwable) { Exceptions.throwIfFatal(t) if (terminated) { RxJavaPlugins.onError(t) } else if (! disposable.isDisposed) { try { observer.onError(t) } catch (inner: Throwable) { Exceptions.throwIfFatal(inner) RxJavaPlugins.onError(CompositeException(t, inner)) } } } try { RetrofitClient.netTracker?.apiRequestInfoTrack(call.request()) } catch (e: Exception) { L.e(e) } } }Copy the code

If you compare it with the official Adapter, we added a paragraph:

If (body is BaseData) {body. CallHolder = originalCall; // If (body is BaseData) {body. CallHolder = originalCall However, if you want to use its request and call information, pay attention to its timeliness.Copy the code

Attach BaseData:

Nullable public class BaseData {/** *} public class BaseData {/** *} public class BaseData {/** *} Public class BaseData {/** *} public class BaseData {/** *} > callHolder; @SerializedName("code") public String code; @SerializedName(value = "msg") public String msg; @Nullable @SerializedName("ext") public JsonElement ext; }Copy the code

Yes, this is just a trick change, we did not change the source, after all, a project has been online, the compatibility requirements for the underlying changes is very high, if the project is just starting to build, it can consider directly changing the event source, of course, this will make the subsequent coding very bad.

At this point we can retrieve the original request in the flow processing of the request result. Let’s ignore the man-machine checksum popup and assume that we’ve got the man-machine checksum behavior parameters via a callback.

We will:

  • Get the original OKHTTP3 Request object from the original call
  • Request follows the Builder design pattern, we can easily convert the Request object into Builder and copy the original corresponding properties, then add the man-machine verification parameters.
  • Recreate the Call using Retrofit’s CallFactory, which is actually OkHttpClient, which implements the CallFactory in Okhttp as mentioned above
  • Make sure onStart is called again in the main thread to make the UI layer a little more elegant, but be aware that the original business has logic that onStart() must come in pairs
  • Re-send the request and reuse the current request callback using the wrapper class

Refer to the following code:

protected void onHumanCheck(@NonNull final retrofit2.Call oldCall, final int code, final Result<T> result) { humanCheckRef = new WeakReference<Function1<HumanCheckParam, Unit>>(new Function1<HumanCheckParam, Unit>() { @Override public Unit invoke(HumanCheckParam humanCheckParam) { if (isDisposed()) return null; if (humanCheckParam == null) { onFailureCode(code, result); return null; } try { Request request = oldCall.request(); Call call = RetrofitClient.getRetrofit().callFactory() .newCall(request.newBuilder() .url(request.url().newBuilder() .setQueryParameter("scene", null2Empty(humanCheckParam.getScene())) .setQueryParameter("sig", null2Empty(humanCheckParam.getSig())) .setQueryParameter("nc_token", null2Empty(humanCheckParam.getNcToken())) .setQueryParameter("csessionid", Null2Empty (humanCheckParam getSessionid ())). The build ()) / / the header length limit, QueryString //.addHeader("scene", null2Empty(humanCheckParam.getScene())) //.addHeader("sig", null2Empty(humanCheckParam.getSig())) // .addHeader("ncToken", null2Empty(humanCheckParam.getNcToken())) // .addHeader("sessionId", null2Empty(hu // manCheckParam.getSessionid())) .build() ); ReComposeOkHttpCallBack.Companion.getMainHandler().post(new Runnable() { @Override public void run() { try { onStart(); } catch (Exception ignore) { } } }); if (ReComposeOkHttpCallBack.Companion.canHandle(oldCall)) { call.enqueue(new ReComposeOkHttpCallBack<T>(oldCall, RetrofitSubscriber.this)); } } catch (Exception e) { e.printStackTrace(); } return null; } private String null2Empty(@Nullable String nullable) { if (nullable == null) return ""; return nullable; }}); HumanChecker.INSTANCE.requestCheck(null, humanCheckRef); } /////// class ReComposeOkHttpCallBack<T>(private val originalCall: Call<Result<T>>, private val subscriber: RetrofitSubscriber<T>) : okhttp3.Callback { companion object { val mainHandler: Handler = Handler(Looper.getMainLooper()) fun canHandle(originalCall: Call<*>): Boolean { return originalCall is OkHttpCall } } override fun onFailure(call: okhttp3.Call, e: IOException) { mainHandler.post { subscriber.onFailure(RetrofitException(RetrofitException.NETWORK, RetrofitException.ERROR_NONE_NETWORK, e)) } } override fun onResponse(call: okhttp3.Call, rawResponse: okhttp3.Response) { val response: Response<Result<T>> try { if (originalCall is OkHttpCall) { response = originalCall.parseResponse(rawResponse) mainHandler.post { try { subscriber.onNext(response.body()) } catch (e: The Exception) {subscriber. OnFailure (RetrofitException (RetrofitException. UNKNOWN, "UNKNOWN error", e)) } finally { try { subscriber.onComplete() } catch (e2: Exception) { } } } } } catch (e: Throwable) {mainHandler. Post {subscriber. OnFailure (RetrofitException (RetrofitException. UNKNOWN, "internal error", e))}}}}Copy the code

conclusion

Going back to the lead FLAG, in this post we try to dig into the design intentions of Retrofit and OkHttp through a few field questions and remember some of the details.

Let’s take a look at OkHttp, which uses Request, Response, and Call to describe the main objects and behaviors in a Request, leaving out the protocol implementation and OKio.

Call is an abstraction and encapsulation of behavior. Its creation relies on Request, which can be executed to get Response, and is designed in both factory and prototype patterns.

Request is the encapsulation of Request, including Request verb, URL, header, body and tag irrelevant to the protocol. It also realizes the factory mode. There are many contents related to the protocol in Response, so it is not expanded this time.

The details in Okhttp basically revolve around three things:

  • Encapsulates HttpUrl, Header and other objects to participate in the abstract expression of the request
  • Request is used by factories to produce calls. In the process of Request, there are many details, but these details are ordered, so AOP design and responsibility chain encapsulation are adopted:
    • A Chain is encapsulated to describe the entire process Chain
    • Interceptor is packaged to describe the facets
    • The aspects are sorted according to the chain of responsibility, in order:
      • interceptors += client.interceptors
      • interceptors += RetryAndFollowUpInterceptor(client)
      • interceptors += BridgeInterceptor(client.cookieJar)
      • interceptors += CacheInterceptor(client.cache)
      • interceptors += ConnectInterceptor
      • if (! forWebSocket) { interceptors += client.networkInterceptors

      }

      • interceptors += CallServerInterceptor(forWebSocket)
  • However, the internal details are too complicated. In order to reduce the user’s cost, OkHttpClient uses Facade mode to encapsulate. If there is a customized requirement for the corresponding module, it will be used when creating OkHttpClient after implementing the corresponding module

Taking a look at Retrofit, which stands for Retrofit, Retrofit alone is not “revolutionary” in Square’s view. Regardless of the underlying optimizations, OkHttp is no better for users than its predecessors. There was still a need for business code to handle Request building, and that’s where Retrofit came in.

He uses annotations to specify the requested header, URL (including path and queryString parameters), and body (most commonly the form), to get this information through reflection, and to use dynamic proxies to encapsulate the Request building underneath.

This is not the end of the story. Using scheduled Callback to handle result callbacks is too restrictive, so a CallAdapter mechanism is provided that allows users to customize the processing.

So far, we’ve reviewed the key source code for Retrofit and OkHttp in three cases, and provided solutions to these two common libraries.