Android 中的 HttpURLConnection 工作正常,但 OkHttp 在 Kitkat 上给出 "Network is unreachable"

HttpURLConnection in Android working fine, but OkHttp gives "Network is unreachable" on Kitkat

我对这个特定站点有疑问:https://tastedive.com/read/api

如果我使用 HttpURLConnection 发出 HTTP 请求,我会得到一个正常的 HTML 响应(在 Android 上,此代码需要在一个单独的线程中,并且还需要进行所有必要的尝试和捕获) :

    StringBuilder result = new StringBuilder();
    URL url = new URL("https://tastedive.com/read/api");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = rd.readLine()) != null) {
        result.append(line);
    }
    rd.close();
    System.out.println(result.toString());  // shows normal HTML response

但是如果我用 OkHttp 来做,用这个代码...

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
            .url("https://tastedive.com/read/api")
            .build();
    client.newCall(request).enqueue(new okhttp3.Callback() {
        @Override
        public void onFailure(okhttp3.Call call, IOException e) {
            Log.d("MY", "failure", e);
        }

        @Override
        public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            } else {
                System.out.println(response.body().string());
            }
        }
    });

...我在 onFailure 中收到此错误:

   java.net.ConnectException: Failed to connect to tastedive.com/2606:4700:30::681c:3a5:443
       at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:242)
       at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:160)
       at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:257)
       at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135)
       at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114)
       at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
       at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
       at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
       at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200)
       at okhttp3.RealCall$AsyncCall.execute(RealCall.java:147)
       at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:841)
    Caused by: java.net.ConnectException: failed to connect to tastedive.com/2606:4700:30::681c:3a5 (port 443) after 10000ms: isConnected failed: ENETUNREACH (Network is unreachable)
       at libcore.io.IoBridge.isConnected(IoBridge.java:223)
       at libcore.io.IoBridge.connectErrno(IoBridge.java:161)
       at libcore.io.IoBridge.connect(IoBridge.java:112)
       at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192)
       at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459)
       at java.net.Socket.connect(Socket.java:843)
       at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:71)
       at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:240)
       at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:160) 
       at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:257) 
       at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135) 
       at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114) 
       at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
       at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
       at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
       at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200) 
       at okhttp3.RealCall$AsyncCall.execute(RealCall.java:147) 
       at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
       at java.lang.Thread.run(Thread.java:841) 
    Caused by: libcore.io.ErrnoException: isConnected failed: ENETUNREACH (Network is unreachable)
       at libcore.io.IoBridge.isConnected(IoBridge.java:208)
       at libcore.io.IoBridge.connectErrno(IoBridge.java:161) 
       at libcore.io.IoBridge.connect(IoBridge.java:112) 
       at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192) 
       at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459) 
       at java.net.Socket.connect(Socket.java:843) 
       at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:71) 
       at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:240) 
       at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:160) 
       at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:257) 
       at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135) 
       at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114) 
       at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
       at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
       at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
       at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
       at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200) 
       at okhttp3.RealCall$AsyncCall.execute(RealCall.java:147) 
       at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32) 
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
       at java.lang.Thread.run(Thread.java:841) 

我使用的这个 OkHttp 来自 Retrofit 2.4.0。 发生在模拟器和真实设备上,但只发生在 Kitkat 上。另请注意,异常会立即抛出,而不仅仅是在 10000 毫秒之后。

我解决了!真实设备实际上得到 Javax.net.ssl.SSLHandshakeException,而模拟器得到 java.net.ConnectException。这个 Whosebug 讨论描述了 Lollipop 之前的设备的 TLS 周围存在一个错误。

所以现在我使用这个答案中的这段代码:,它解决了模拟器异常和设备异常:

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
                .supportsTlsExtensions(true)
                .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
                .cipherSuites(
                        CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                        CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                        CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
                        CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
                        CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
                        CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
                        CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
                        CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
                        CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
                        CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
                        CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
                        CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA)
                .build();
        OkHttpClient client = new OkHttpClient.Builder()
                .connectionSpecs(Collections.singletonList(spec))
                .build();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://tastedive.com")
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();