为什么 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,但我在问题中提到的代理查找代码似乎不存在。
(这是 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,但我在问题中提到的代理查找代码似乎不存在。