retrofit icon indicating copy to clipboard operation
retrofit copied to clipboard

How to set call timeout dynamically for a particular request?

Open filipfriskan opened this issue 4 years ago • 12 comments

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.

filipfriskan avatar Jul 01 '20 14:07 filipfriskan

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.

ctlove0523 avatar Jul 05 '20 03:07 ctlove0523

@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);
                }

ctlove0523 avatar Jul 07 '20 14:07 ctlove0523

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/ ).

filipfriskan avatar Jul 08 '20 07:07 filipfriskan

@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.

ctlove0523 avatar Jul 08 '20 07:07 ctlove0523

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.

leobert-lan avatar Jul 09 '20 06:07 leobert-lan

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.

filipfriskan avatar Jul 09 '20 07:07 filipfriskan

Yes,it's dangerous and time wasting to use reflection, maybe okhttp has realized it in the lastest version. waiting the best solution💪💪💪

leobert-lan avatar Jul 09 '20 11:07 leobert-lan

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. 😕

filipfriskan avatar Jul 09 '20 11:07 filipfriskan

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.

swankjesse avatar Jul 09 '20 14:07 swankjesse

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

ctlove0523 avatar Jul 09 '20 14:07 ctlove0523

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

leobert-lan avatar Oct 27 '20 11:10 leobert-lan

umm,Jake Wharton have given a solution here, https://github.com/square/retrofit/issues/2982 #2982 。

leobert-lan avatar Oct 28 '20 01:10 leobert-lan