1

(This is Java 8 and Tomcat 8)

I have a simple websocket client test app that I am trying to test against a squid proxy. I am using the http[s].proxy[Host|Port] system properties to set the proxy info on a JVM-wide level.

When I use the Tyrus implementation (tyrus-standalone-client-1.12.jar) the proxy system properties are honored (of course, then I have the problem that wss URLs won't work, but that's a different question).

However, when I use the Tomcat implementation (tomcat-websocket.jar) the system proxy settings are empirically ignored (I'm using a network sniffer and I see the packets are not going to the proxy whereas with Tyrus I do see the packets going to the proxy).

I looked at the source to org.apache.tomcat.websocket.WsWebSocketContainer.connectToServer() and it sure looks like it is picking up the proxy info and using it:

    // Check to see if a proxy is configured. Javadoc indicates return value
    // will never be null
    List<Proxy> proxies = ProxySelector.getDefault().select(proxyPath);
    Proxy selectedProxy = null;
    for (Proxy proxy : proxies) {
        if (proxy.type().equals(Proxy.Type.HTTP)) {
            sa = proxy.address();
            if (sa instanceof InetSocketAddress) {
                InetSocketAddress inet = (InetSocketAddress) sa;
                if (inet.isUnresolved()) {
                    sa = new InetSocketAddress(inet.getHostName(), inet.getPort());
                }
            }
            selectedProxy = proxy;
            break;
        }
    }

I put the piece of that code that looks up the proxies into my test program to see what it was discovering and as you'll see, it picks up the proxy info. So I don't know why Tomcat websockets aren't honoring the properties.

My test app:

import javax.websocket.*;

import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

/**
 * This class is adapted from http://stackoverflow.com/a/26454417/411393 which
 * is an answer to http://stackoverflow.com/questions/26452903/javax-websocket-client-simple-example/
 */
public class WebsocketTestApp
{
    public static void main(String[] args) {
        try {
            // Determine where to connect
            final URI servicePath;
            if ((args.length != 0) && "secure".equals(args[0])) {
                // servicePath = new URI("wss://real.okcoin.cn:10440/websocket/okcoinapi");
                servicePath = new URI("wss://echo.websocket.org/");
            }
            else {
                servicePath = new URI("ws://echo.websocket.org/");
            }

            // See what Java thinks the proxy for the service is.
            URI proxyPath = buildProxyPath(servicePath);
            System.out.println("service path: " + servicePath);
            System.out.println("proxy path:   " + proxyPath);

            // This line is copied from the source to org.apache.tomcat.websocket.WsWebSocketContainer (approx line 254)
            List<Proxy> proxies = ProxySelector.getDefault().select(proxyPath);
            for (Proxy proxy : proxies) {
                System.out.println(proxy.toString());
            }

            // open websocket
            final WebsocketClientEndpoint clientEndPoint = new WebsocketClientEndpoint(servicePath);

            // add listener
            clientEndPoint.addMessageHandler(System.out::println);

            // send message to websocket
            clientEndPoint.sendMessage("{'event':'addChannel','channel':'ok_btccny_ticker'}");

            // wait 2 seconds for messages from websocket
            Thread.sleep(2000);
        }
        catch (InterruptedException ex) {
            System.err.println("InterruptedException exception: " + ex.getMessage());
        }
        catch (URISyntaxException ex) {
            System.err.println("URISyntaxException exception: " + ex.getMessage());
        }
    }

    // This is essentially copied from the source to org.apache.tomcat.websocket.WsWebSocketContainer (approx lines 230-241)
    private static URI buildProxyPath(URI path) {
        URI proxyPath;

        // Validate scheme (and build proxyPath)
        String scheme = path.getScheme();
        if ("ws".equalsIgnoreCase(scheme)) {
            proxyPath = URI.create("http" + path.toString().substring(2));
        }
        else if ("wss".equalsIgnoreCase(scheme)) {
            proxyPath = URI.create("https" + path.toString().substring(3));
        }
        else {
            throw new IllegalArgumentException("wsWebSocketContainer.pathWrongScheme: " + scheme);
        }

        return proxyPath;
    }

    @ClientEndpoint
    public static class WebsocketClientEndpoint
    {

        Session userSession = null;
        private MessageHandler messageHandler;

        public WebsocketClientEndpoint(URI endpointURI) {
            try {
                WebSocketContainer container = ContainerProvider.getWebSocketContainer();
                //((ClientManager)container).getProperties().put(ClientProperties.PROXY_URI, "http://172.16.99.15:3128");
                container.connectToServer(this, endpointURI);
            }
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Callback hook for Connection open events.
         *
         * @param userSession the userSession which is opened.
         */
        @OnOpen
        public void onOpen(Session userSession) {
            System.out.println("opening websocket");
            this.userSession = userSession;
        }

        /**
         * Callback hook for Connection close events.
         *
         * @param userSession the userSession which is getting closed.
         * @param reason      the reason for connection close
         */
        @OnClose
        public void onClose(Session userSession, CloseReason reason) {
            System.out.println("closing websocket");
            this.userSession = null;
        }

        /**
         * Callback hook for Message Events. This method will be invoked when a client send a message.
         *
         * @param message The text message
         */
        @OnMessage
        public void onMessage(String message) {
            if (this.messageHandler != null) {
                this.messageHandler.handleMessage(message);
            }
        }

        /**
         * register message handler
         *
         * @param msgHandler
         */
        public void addMessageHandler(MessageHandler msgHandler) {
            this.messageHandler = msgHandler;
        }

        /**
         * Send a message.
         *
         * @param message
         */
        public void sendMessage(String message) {
            this.userSession.getAsyncRemote().sendText(message);
        }

        /**
         * Message handler.
         */
        @FunctionalInterface
        public static interface MessageHandler
        {
            public void handleMessage(String message);
        }
    }
}

And when I run it the output is:

service path: ws://echo.websocket.org/
proxy path:   http://echo.websocket.org/
HTTP @ 172.16.99.15:3128
opening websocket
{'event':'addChannel','channel':'ok_btccny_ticker'}

So it is printing out the correct proxy information, using the same lookup code that WsWebSocketContainer.connectToServer() is using, but the proxy info is not honored and a direct connection is being made instead.

Is this some weird bug in the Tomcat implementation? Is the Tomcat implementation known to ignore the proxy property settings?


UPDATE:

I also put the code into a quick & dirty servlet in a running Tomcat and the same thing happens. While Tomcat overall seems to honor the settings (calling java.net.URL.openStream() in Tomcat honors the settings) the websocket stuff again refuses to honor the settings. So whatever is causing this it was not the code being run standalone rather than in a Tomcat container.

QuantumMechanic
  • 13,795
  • 4
  • 45
  • 66

1 Answers1

0

The reason is that I'm an idiot.

I am running with Tomcat 8.0.23 (that's a requirement external to me).

However, I forgot that and downloaded the source to 8.0.33.

I've gone back and downloaded the source to 8.0.23 and the proxy lookup code I was talking about in my question does not appear to be there.

QuantumMechanic
  • 13,795
  • 4
  • 45
  • 66