3

I am using okhttp with ribbon using spring. I wish to retry on another service instance on connect timeout but not on read timeout(for obvious reasons). This retry will be handled by a custom ribbon retry handler. But to do the above, I need to distinguish between read and connect timeouts. The behaviour with okhttp is as follows:

  • On read timeout:

    java.net.SocketTimeoutException: timeout
        at okio.Okio$3.newTimeoutException(Okio.java:212)
        at okio.AsyncTimeout.exit(AsyncTimeout.java:288)
        at okio.AsyncTimeout$2.read(AsyncTimeout.java:242)
        at okio.RealBufferedSource.indexOf(RealBufferedSource.java:325)
        at okio.RealBufferedSource.indexOf(RealBufferedSource.java:314)
        at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:210)
        at okhttp3.internal.http.Http1xStream.readResponse(Http1xStream.java:184)
        at okhttp3.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:125)
        at okhttp3.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:775)
        at okhttp3.internal.http.HttpEngine.access$200(HttpEngine.java:86)
        at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:760)
        at okhttp3.internal.http.HttpEngine.readResponse(HttpEngine.java:613)
        at okhttp3.RealCall.getResponse(RealCall.java:244)
        at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:201)
        at com.bluejeans.sample.test.OkHttpConnect$1.intercept(OkHttpConnect.java:25)
        at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:190)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:163)
        at okhttp3.RealCall.execute(RealCall.java:57)
        at com.bluejeans.sample.test.OkHttpConnect.main(OkHttpConnect.java:39)
    Caused by: java.net.SocketTimeoutException: Read timed out
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:170)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at okio.Okio$2.read(Okio.java:140)
        at okio.AsyncTimeout$2.read(AsyncTimeout.java:238)
        ... 16 more
    
  • On connect timeout:

     java.net.SocketTimeoutException: connect timed out
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:589)
        at okhttp3.internal.Platform.connectSocket(Platform.java:121)
        at okhttp3.internal.io.RealConnection.connectSocket(RealConnection.java:185)
        at okhttp3.internal.io.RealConnection.buildConnection(RealConnection.java:170)
        at okhttp3.internal.io.RealConnection.connect(RealConnection.java:111)
        at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:187)
        at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:123)
        at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:93)
        at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:296)
        at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
        at okhttp3.RealCall.getResponse(RealCall.java:243)
        at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:201)
        at com.bluejeans.sample.test.OkHttpConnect$1.intercept(OkHttpConnect.java:25)
        at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:190)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:163)
        at okhttp3.RealCall.execute(RealCall.java:57)
        at com.bluejeans.sample.test.OkHttpConnect.main(OkHttpConnect.java:39)
    

Deciding based on the exception message doesn't seem like a good idea. What is the recommended way to achieve this?

g00glen00b
  • 41,995
  • 13
  • 95
  • 133
Tanmay
  • 366
  • 1
  • 4
  • 22
  • Stack traces please. – user207421 Apr 30 '17 at 10:42
  • You know whether you are doing a connect or a read. You can catch the exceptions separately in each case. There is no difficulty. – user207421 Apr 30 '17 at 22:38
  • 1
    I am merely making an http call. The exception is thrown by the okhttp library. The problem is that the same exception is thrown by the okhttp in both cases and I am not able decipher whether it is a read timeout or a connect timeout using just the class of the exception. – Tanmay May 01 '17 at 11:43

1 Answers1

0

If you do not want to rely on the message you may also choose the following options. However both of them need JUnit coverage because the developers of OkHttp may change their implementation in future.

  1. Use the cause of the exception to decide how to treat the request:

    try {
        ...
    } catch (SocketTimeoutException e) {
        if (e.getCause() != null && e.getCause() instanceof SocketTimeoutException) {
            // read timeout
        } else {
            // socket timeout
        }
    }
    
  2. Loop through the stack trace:

    try {
        ...
    } catch (SocketTimeoutException e) {
        boolean isConnectException = false;
        for (StackTraceElement elem : e.getStackTrace()) {
            if ("java.net.Socket".equals(elem.getClassName()) &&
                    "connect".equals(elem.getMethodName())) {
                isConnectException = true;
                break;
            }
        }
        ...
    }
Franz Fellner
  • 563
  • 1
  • 5
  • 13