retrofit
retrofit copied to clipboard
How to set call timeout dynamically for a particular request?
Is it possible to dynamically set call timeout for a particular request?
I have set a default call timeout when building the OkHttp client and I would not want to create a new instance with different call timeout for one request. I have seen that is possible to create an interceptor like explained in https://github.com/square/retrofit/issues/2561#issuecomment-345641958 but call timeout cannot be set on chain object ( like read, write and connect timeouts). Timeout object can be fetched from chain.call() but cannot set a new timeout with different value.
We can chanage read timeout,write timeout and connection timeout use okhttp's interceptor like the follow code does:
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(Duration.ofSeconds(1))
.addInterceptor(chain -> {
Request request = chain.request();
Interceptor.Chain newChain = chain.withReadTimeout(10,TimeUnit.SECONDS);
return newChain.proceed(request);
})
.build();
but how this works with retrofit need more research and may be must modify retrofit's code.
@filipconsulteer You can pass parameters of timeout,like readTimeout,writeTimeOut or connectTimeOut through HTTP headers,Suppose the parameters‘s name is 'Read-Time-Out','Write-Time-Out' and 'Connect-Time-Out',then add Interceptor to intercept the request and read the value,use the value to rebuild Chain.The code like this:
public Response intercept(@NotNull Chain chain) throws IOException {
Request request = chain.request();
String readTimeOutStr = request.header("Read-Time-Out");
String writeTimeOutStr = request.header("Write-Time-Out");
String connectTimeOutStr = request.header("Connect-Time-Out");
int readTimeOut = readTimeOutStr == null ? chain.readTimeoutMillis() : Integer.parseInt(readTimeOutStr);
int writeTimeOut = writeTimeOutStr == null ? chain.writeTimeoutMillis() : Integer.parseInt(writeTimeOutStr);
int connectTimeOut = connectTimeOutStr == null ? chain.connectTimeoutMillis() : Integer.parseInt(connectTimeOutStr);
Chain overrideChain = chain.withReadTimeout(readTimeOut, TimeUnit.MILLISECONDS)
.withWriteTimeout(writeTimeOut, TimeUnit.MILLISECONDS)
.withConnectTimeout(connectTimeOut, TimeUnit.MILLISECONDS);
return overrideChain.proceed(request);
}
Thank you for your response @ctlove0523! But that was not my question. What I am trying to set dynamically is CALL timeout ( https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/call-timeout/ ).
@filipconsulteer oh I'm sorry,CALL timeout is a property of OkHttpClient,dynamic change the value may be hard,but I like to work with you to find a way.
the response of @ctlove0523 shows "How to use headers-define and retrofit-interceptor to change the time out setting in retrofit for a specified request". And in okhttp, AsyncTimeOut was used to controll the timeout-emit in whole period of one request.
e.g. okhttp3.internal.connection.Transmitter (okhttp-4.3.1):
private val timeout = object : AsyncTimeout() {
override fun timedOut() {
cancel()
}
}.apply {
timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
}
we can define a custom OkHttpClient to generate RealCall for Retrofit, like this:
static class OkHttpClient2 extends OkHttpClient {
public OkHttpClient2(@NotNull Builder builder) {
super(builder);
}
@NotNull
@Override
public Call newCall(@NotNull Request request) {
// no meaning for the issue,just lazy to remove
request = request.newBuilder().tag(NetLoggingEventListener.RetrofitRequest.class,
new NetLoggingEventListener.RetrofitRequest(System.currentTimeMillis())).build();
// we can read the threshold in header of the request,and use reflection to change the call
return super.newCall(request);
}
static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) {
return new OkHttpClient2(client.newBuilder());
}
}
Define whole timeout threshold in headers of the request, then, we can find it in the request's header, and then, use 'Reflection' to change the internal timeout controller in RealCall. Caution with the version of Okhttp
This may be a solution.
Interesting solution @leobert-lan , but I would not prefer to change the timeout object via reflection. I think this should be properly implemented in OkHttp to have a standard way to change CALL timeout.
Yes,it's dangerous and time wasting to use reflection, maybe okhttp has realized it in the lastest version. waiting the best solution💪💪💪
I have researched the latest release notes and documentation of OkHttp ( https://square.github.io/okhttp/4.x/okhttp/okhttp3/-interceptor/-chain/ ) and I did not find a way to set CALL timeout via interceptor inside a chain object. Currently only read, write and connect timeouts can be set. 😕
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.
the response of @ctlove0523 shows "How to use headers-define and retrofit-interceptor to change the time out setting in retrofit for a specified request". And in okhttp, AsyncTimeOut was used to controll the timeout-emit in whole period of one request.
e.g. okhttp3.internal.connection.Transmitter (okhttp-4.3.1):
private val timeout = object : AsyncTimeout() { override fun timedOut() { cancel() } }.apply { timeout(client.callTimeoutMillis.toLong(), MILLISECONDS) }
we can define a custom OkHttpClient to generate RealCall for Retrofit, like this:
static class OkHttpClient2 extends OkHttpClient { public OkHttpClient2(@NotNull Builder builder) { super(builder); } @NotNull @Override public Call newCall(@NotNull Request request) { // no meaning for the issue,just lazy to remove request = request.newBuilder().tag(NetLoggingEventListener.RetrofitRequest.class, new NetLoggingEventListener.RetrofitRequest(System.currentTimeMillis())).build(); // we can read the threshold in header of the request,and use reflection to change the call return super.newCall(request); } static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) { return new OkHttpClient2(client.newBuilder()); } }
Define whole timeout threshold in headers of the request, then, we can find it in the request's header, and then, use 'Reflection' to change the internal timeout controller in RealCall. Caution with the version of Okhttp
This may be a solution.
good idea
I notice that the interface okhttp3.Call
has exposed a api function called fun timeout(): Timeout
, maybe it's works to invoke okio.Timeout.kt#timeout(timeout: Long, unit: TimeUnit): Timeout
to change the timeout mechanism.
e.g.:
static class OkHttpClient2 extends OkHttpClient {
public OkHttpClient2(@NotNull Builder builder) {
super(builder);
}
@NotNull
@Override
public Call newCall(@NotNull Request request) {
Call call = super.newCall(request);
//同样利用header设置timeout,此处取值略
call.timeout().timeout(3000,TimeUnit.MILLISECONDS);
return call;
}
static OkHttpClient2 reBuilder(@NonNull OkHttpClient client) {
return new OkHttpClient2(client.newBuilder());
}
}
not unit tested
umm,Jake Wharton have given a solution here, https://github.com/square/retrofit/issues/2982 #2982 。