反向代理支持WebSocket

已经利用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没有正确的初始化,没有指定证书路径

参考

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值