为什么 Tomcat websockets 不遵守 JVM 范围的 HTTP 代理设置?

Why are Tomcat websockets not honoring JVM-wide HTTP proxy settings?

(这是 Java 8 和 Tomcat 8)

我有一个简单的 websocket 客户端测试应用程序,我正在尝试针对 squid 代理进行测试。我正在使用 http[s].proxy[Host|Port] 系统属性在 JVM 范围内设置代理信息。

当我使用 Tyrus 实现 (tyrus-standalone-client-1.12.jar) 时,代理系统属性受到尊重(当然,然后我遇到 wss URL 不起作用的问题,但这是另一个问题).

但是,当我使用 Tomcat 实现 (tomcat-websocket.jar) 时,系统代理设置根据经验被忽略(我正在使用网络嗅探器,我发现数据包没有发送到代理而对于 Tyrus,我确实看到了发送到代理的数据包。

我查看了 org.apache.tomcat.websocket.WsWebSocketContainer.connectToServer() 的源代码,它确实看起来正在获取并使用代理信息:

    // 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;
        }
    }

我将查找代理的那段代码放入我的测试程序中,以查看它发现了什么,正如您将看到的,它会获取代理信息。所以我不知道为什么 Tomcat websockets 不遵守这些属性。

我的测试应用:

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  which
 * is an answer to 
 */
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);
        }
    }
}

当我 运行 它的输出是:

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'}

所以它打印出正确的代理信息,使用与 WsWebSocketContainer.connectToServer() 相同的查找代码,但代理信息未被接受,而是建立了直接连接。

这是 Tomcat 实现中的一些奇怪错误吗? Tomcat 实现是否已知会忽略代理 属性 设置?


更新:

我还在 运行ning Tomcat 中将代码放入一个快速且肮脏的 servlet 中,同样的事情发生了。虽然 Tomcat 总体上似乎遵守这些设置(在 Tomcat 中调用 java.net.URL.openStream() 遵守这些设置),但 websocket 的东西再次拒绝遵守这些设置。因此,无论是什么原因造成的,它 而不是 代码是 运行 独立的,而不是在 Tomcat 容器中。

原因是我是个白痴

我是 运行 Tomcat 8.0.23(这是我的外部要求)。

不过,我忘记了,把源码下载到了8.0.33.

我已经返回并将源代码下载到 8.0.23,但我在问题中提到的代理查找代码似乎不存在。