square / okhttp

Square’s meticulous HTTP client for the JVM, Android, and GraalVM.
https://square.github.io/okhttp/
Apache License 2.0
45.71k stars 9.15k forks source link

Proxy Authentication failed when create websocket client via proxy #4321

Open cdfeng opened 5 years ago

cdfeng commented 5 years ago

Proxy Authentication failed when create websocket client via http proxy server

I am trying to create websocket client via http proxy server. But I get 407 http response, here is my code snippet and screenshot. Is there anything wrong with my code? Thank you verymuch!

code snippet:

       OkHttpClient client = new OkHttpClient.Builder()
                .proxy(proxy)
                .proxyAuthenticator(new Authenticator() {
                    @Override
                    public Request authenticate(Route route, Response response) throws IOException {

                        if (response.request().header("Proxy-Authorization") != null) {
                            return null; // Give up, we've already failed to authenticate.
                        }
                        System.out.println("proxy authorization...");
                        String credential = Credentials.basic(proxyUsername, proxyPassword);
                        Request returnObj = response.request().newBuilder()
                                .header("Proxy-Authorization", credential)
                                .build();
                        return returnObj;
                    }
                })
                .build();

        client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                System.out.println(text);
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                System.out.println(process(bytes));
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                System.out.println("closing , reason: "+reason);
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                System.out.println("closed, reason: " + reason);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                System.out.println("failure, reason: " + t);
            }
        });

screenshot:

image

jaredkaczynski commented 5 years ago

So this is the same issue people had a couple years ago and the same issue I'm facing. It's something with the SSL URL path over the non-SSL path. When running a non SSL (HTTP) URL the result after the authenticator is called is:

HTTP/1.1 301 Moved Permanently

While for SSL (HTTPS) based urls it's the text in the associated error. I've been digging all day for the specific reason but, considering it's only operating incorrectly on SSL based URLs I would assume it's a library issue.

Debugging on the function readHeaders on 221 doesn't seem to result in a difference. The leftover lines for both the SSL and Non-SSL libraries are the same being the .... text. The source object doesn't seem to get updated with new data however so that may be part of it. The data left after the empty line is thus the first line for the next iteration and thus not an HTTP line but some HTML.

I'll keep digging though because I also want it to work properly. My current thoughts are something with createTunnel in RealConnection. It seems to be the main difference between SSL/nonSSL requests.

Apache's HttpComponents also return success.

jaredkaczynski commented 5 years ago

As a followup. An example proxy that fails is 3proxy. I used this script to setup on one of my VPS's and get the error result. https://github.com/benjamin74/3proxy

For an example test that fails...

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertNotEquals;

public class Main {
    static Authenticator proxyAuthenticator = (route, response) -> {
        String credential = Credentials.basic("proxyuser1", "StrongPasswordforUser");
        return response.request().newBuilder()
                .header("Proxy-Authorization", credential)
                .build();
    };
    public static void main(String[] args) {
        ConnectionPool pool = new ConnectionPool(1, 1000, TimeUnit.MILLISECONDS);
        OkHttpClient client = new OkHttpClient.Builder()
                .connectionPool(pool).connectTimeout(2, TimeUnit.SECONDS).readTimeout(2, TimeUnit.SECONDS)
                .build();
        client = client.newBuilder().proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("", 3128))).proxyAuthenticator(proxyAuthenticator).build();

        Request request = new Request.Builder()
                //works fully
                //.url("http://stealmylogin.com")
                //works if http not if https
                //.url("https://google.com")
                //Doesn't work with either
                .url("http://kith.com")
                .build();

        int responseCode = 0;

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                responseCode = response.code();
                System.out.println("Fail Get");
                throw new IOException("Timed Out " + response);
            } else {
                responseCode = response.code();
                System.out.println("Success");
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Fail Proxy");
        }
        assertNotEquals(0, responseCode);
    }
}

If needed I can provide a proxy that fails. This ONLY happens with HTTPS websites. Sites without SSL work fine.

Some more investigation and understanding. So the failure happens in createTunnel in RealConnection. On the first auth request it fails and goes to a failure case, then the error occurs reading the newly sent request.