已经利用Jetty的ProxyServlet实现了反向代理的功能,但是无法支持WebSocket。
- 利用Jetty的ProxyServlet实现反向代理:关键点在于rewriteTarget,在这个方法中改写目标URL,从而实现把HTTP请求“转发”给对应的目标
一开始对WebSocket有误解,以为WebSocket也是基于HTTP协议,只是报文头有些特殊,想通过ProxyServlet进行特殊处理,结果自然行不通。回过头来仔细分析发现WebSocket实际上是另外的协议,只是握手借用了HTTP协议,端口借用了HTTP,因此只能采取“隧道”的方案:
WebSocketServerServlet:
public class WebSocketServerServlet extends WebSocketServlet {
private static final Logger log = LoggerFactory.getLogger (WebSocketServerServlet.class);
private static final long WEBSOCKET_DEFAULT_MAX_IDLE = 60000; // 1 minute
private static final int WEBSOCKET_DEFAULT_MAX_BUFF = 1024 * 1024; // 1 mb
......
@Override
public void configure(WebSocketServletFactory factory) {
log.info ("Configuring the web socket adapter");
factory.getPolicy ().setIdleTimeout (WEBSOCKET_DEFAULT_MAX_IDLE);
factory.getPolicy ().setMaxBinaryMessageSize (WEBSOCKET_DEFAULT_MAX_BUFF);
factory.getPolicy ().setMaxTextMessageBufferSize (WEBSOCKET_DEFAULT_MAX_BUFF);
factory.setCreator(new WebSocketServerCreator());
}
}
WebSocketServerCreator:
public class WebSocketServerCreator implements WebSocketCreator {
private WebSocketServerAdapter websocket = new WebSocketServerAdapter();
@Override
public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) {
for (String subprotocol : servletUpgradeRequest.getSubProtocols())
{
if ("binary".equals(subprotocol)) {
servletUpgradeResponse.setAcceptedSubProtocol(subprotocol);
return websocket;
}
}
// No valid subprotocol in request, ignore the request
return null;
}
}
WebSocketServerAdapter :
public class WebSocketServerAdapter extends WebSocketAdapter {
private static final Logger log = LoggerFactory.getLogger (WebSocketServerAdapter.class);
private SslContextFactory sslContextFactory;
private WebSocketClient webSocketClient;
private Session proxyingSession;
private static final String WEBSOCKET_URL_FORMAT = "wss://%s%s";
public WebSocketServerAdapter() {
initSslContextFactory();
this.webSocketClient = new WebSocketClient(this.sslContextFactory);
}
private void initSslContextFactory() {
// initialize SslContextFactory
this.sslContextFactory = new SslContextFactory();
SSLContext sslContext = ....
this.sslContextFactory.setSslContext(sslContext);
ArrayList<String> ciphersToEnable = new ArrayList<> (sslContext.getDefaultSSLParameters ().getCipherSuites ().length + 1);
ciphersToEnable.add (".*_GCM_.*"); // GCM first in case ordering is honored
ciphersToEnable.addAll (Arrays.asList (sslContext.getDefaultSSLParameters ().getCipherSuites ()));
this.sslContextFactory.setIncludeCipherSuites (ciphersToEnable.toArray (new String[ciphersToEnable.size ()]));
this.sslContextFactory.setExcludeCipherSuites (".*[Aa][Nn][Oo][Nn].*", ".*[Nn][Uu][Ll][Ll].*");
}
@Override
public void onWebSocketConnect (Session sess) {
super.onWebSocketConnect (sess);
String ip="1.2.3.4"; // target ip
String path="/test"; // url path
String dest= String.format(WEBSOCKET_URL_FORMAT, ip, path);
try {
URI destUri = new URI(dest);
webSocketClient.start();
Future<Session> future = webSocketClient.connect(new WebSocketClientAdapter(sess), destUri, getClientUpgradeRequest(sess));
proxyingSession = future.get();
if(proxyingSession != null && proxyingSession.isOpen()) {
log.debug("websocket connected to {}", dest);
}
} catch (URISyntaxException e) {
log.error("invalid url: {}", dest);
} catch (IOException | ExecutionException | InterruptedException e) {
log.error("exception while connecting to {}", dest, e);
} catch (Exception e) {
log.error("exception while starting websocket client", e);
}
}
@Override
public void onWebSocketText (String message) {
super.onWebSocketText (message);
log.debug("websocket message received {}", message);
// forwarding ...
if(proxyingSession != null && proxyingSession.isOpen()) {
try {
proxyingSession.getRemote().sendString(message);
} catch (IOException e) {
log.error("exception while forwarding text message to client", e);
}
} else {
log.error("proxying session is null or closed.");
}
}
@Override
public void onWebSocketBinary(byte[] payload, int offset, int len) {
super.onWebSocketBinary(payload, offset, len);
log.debug("websocket binary received, offset:{}, len: {}", offset, len);
// forwarding ...
if(proxyingSession != null && proxyingSession.isOpen()) {
ByteBuffer byteBuffer = ByteBuffer.wrap(payload, offset, len);
try {
this.proxyingSession.getRemote().sendBytes(byteBuffer);
} catch (IOException e) {
log.error("exception while forwarding text message to client", e);
}
} else {
log.error("proxying session is null or closed.");
}
}
@Override
public void onWebSocketClose (int statusCode, String reason) {
super.onWebSocketClose (statusCode, reason);
log.debug ("Socket Closed: status code: [ {} ]", statusCode);
disconnect ();
}
private ClientUpgradeRequest getClientUpgradeRequest(Session sess) {
ClientUpgradeRequest request = new ClientUpgradeRequest();
UpgradeRequest upgradeRequest = sess.getUpgradeRequest();
request.setCookies(upgradeRequest.getCookies());
request.setSubProtocols(upgradeRequest.getSubProtocols());
Map<String, List<String>> headers = upgradeRequest.getHeaders();
headers.forEach((k,v) -> {
if(!k.equalsIgnoreCase(HttpHeader.HOST.asString())) {
request.setHeader(k, v);
}
});
return request;
}
/**
* This method unregisters the subscriber and closes the connection
*
* @return
*/
private boolean disconnect () {
if (this.getSession () == null || !this.getSession ().isOpen ()) {
return true;
}
try {
this.getSession ().disconnect ();
if (isConnected ()) {
log.debug ("Could not disconnect the websocket client");
return false;
}
}
catch (Exception e) {
log.error ("Exception on disconnecting the websocket client");
} finally {
if(this.webSocketClient != null) {
try {
this.webSocketClient.stop();
} catch (Exception e) {
log.error ("Exception while stopping websocket client", e);
}
}
}
return true;
}
}
WebSocketClientAdapter:
public class WebSocketClientAdapter extends WebSocketAdapter {
private static final Logger log = LoggerFactory.getLogger (WebSocketClientAdapter.class);
private Session proxyingSess;
WebSocketClientAdapter(Session sess) {
this.proxyingSess = sess;
}
@Override
public void onWebSocketConnect (Session sess) {
super.onWebSocketConnect (sess);
log.debug("websocket client connected ...");
}
@Override
public void onWebSocketText (String message) {
super.onWebSocketText (message);
log.debug("websocket message received {}", message);
// forwarding
if(this.proxyingSess != null && this.proxyingSess.isOpen()) {
try {
this.proxyingSess.getRemote().sendString(message);
} catch (IOException e) {
log.error("exception while forwarding text message to client", e);
}
} else {
log.error("proxying session is null or closed.");
}
}
@Override
public void onWebSocketBinary(byte[] payload, int offset, int len) {
super.onWebSocketBinary(payload, offset, len);
log.debug("websocket binary received, offset:{}, len: {}", offset, len);
// forwarding ...
ByteBuffer byteBuffer = ByteBuffer.wrap(payload, offset, len);
if(this.proxyingSess != null && this.proxyingSess.isOpen()) {
try {
this.proxyingSess.getRemote().sendBytes(byteBuffer);
} catch (IOException e) {
log.error("exception while forwarding binary to client", e);
}
} else {
log.error("proxying session is null or closed.");
}
}
@Override
public void onWebSocketClose (int statusCode, String reason) {
super.onWebSocketClose (statusCode, reason);
log.debug ("Socket Closed: status code: [ {} ]", statusCode);
disconnect ();
}
/**
* This method unregisters the subscriber and closes the connection
*
* @return
*/
private boolean disconnect () {
if (this.getSession () == null || !this.getSession ().isOpen ()) {
return true;
}
try {
this.getSession ().disconnect ();
if (isConnected ()) {
log.debug ("Could not disconnect the websocket client");
return false;
}
}
catch (Exception e) {
log.error ("Exception on disconnecting the websocket client");
}
return true;
}
}
问题
- WebSocketClient connect 抛出异常:java.lang.IllegalStateException: WebSocketClient@470192252 is not started
原因:在connect前需要先调用start方法 - WebSocketClient connect 抛出异常:java.util.concurrent.ExecutionException: java.io.IOException: Cannot init SSL
原因:wss连接,WebSocketClient初始化时需要传入SslContextFactory实例 - WebSocketClient connect 抛出异常:java.util.concurrent.ExecutionException: javax.net.ssl.SSLHandshakeException: General SSLEngine problem…
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
原因:SslContextFactory没有正确的初始化,没有指定证书路径