/* * Copyright 2013 Eediom Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.araqne.logdb.client.http.impl; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeoutException; import org.araqne.logdb.client.AbstractLogDbSession; import org.araqne.logdb.client.Message; import org.araqne.logdb.client.MessageException; import org.araqne.logdb.client.Message.Type; import org.araqne.websocket.WebSocket; import org.araqne.websocket.WebSocketConfig; import org.araqne.websocket.WebSocketListener; import org.araqne.websocket.WebSocketMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 웹소켓 프로토콜을 이용하여 메시지버스 RPC 통신을 구현합니다. * * @since 0.5.0 * @author xeraph@eediom.com * */ public class WebSocketSession extends AbstractLogDbSession implements WebSocketListener { private static final int DEFAULT_READ_TIMEOUT = 10000; private final Logger logger = LoggerFactory.getLogger(WebSocketSession.class); private final Logger sendLog = LoggerFactory.getLogger("websocket-send"); private Object sendLock = new Object(); private WebSocket websocket; private WebSocketBlockingTable table = new WebSocketBlockingTable(this); @Override public String toString() { return "WebSocketSession [" + websocket + "]"; } private final Timer timer; public WebSocketSession(String host, int port) throws IOException { this(host, port, false, false, 0); } public WebSocketSession(String host, int port, boolean secure, boolean skipCertCheck) throws IOException { this(host, port, secure, skipCertCheck, 0); } public WebSocketSession(String host, int port, boolean secure, boolean skipCertCheck, int connectTimeout) throws IOException { this(host, port, secure, skipCertCheck, connectTimeout, DEFAULT_READ_TIMEOUT); } public WebSocketSession(String host, int port, boolean secure, boolean skipCertCheck, int connectTimeout, int readTimeout) throws IOException { URI uri = null; try { String scheme = secure ? "wss://" : "ws://"; uri = new URI(scheme + host + ":" + port + "/websocket"); this.websocket = new WebSocket(new WebSocketConfig().setUri(uri).setSkipCertCheck(skipCertCheck) .setConnectTimeout(connectTimeout).setReadTimeout(readTimeout)); websocket.addListener(this); } catch (URISyntaxException e) { throw new IllegalArgumentException("invalid host: " + host); } int localPort = websocket.getLocalPort(); this.timer = new Timer( String.format("WebSocket Ping Timer [:%d<->%s]", localPort, uri), true); timer.scheduleAtFixedRate(new PingTask(), new Date(), 2000); } @Override public boolean isClosed() { return websocket.isClosed(); } @Override public Message rpc(Message req, int timeout) throws IOException, TimeoutException { WaitingCall call = table.set(req.getGuid()); String json = MessageCodec.encode(req); synchronized (sendLock) { sendLog.debug("araqne logdb client: send rpc [{}]", json); websocket.send(json); } // wait response infinitely Message m; try { if (timeout == 0) m = table.await(call); else m = table.await(call, timeout); } catch (InterruptedException e) { throw new RuntimeException("interrupted: " + e.getMessage()); } if (m.getErrorCode() != null) throw new MessageException(m.getErrorCode(), m.getErrorMessage(), m.getParameters()); return m; } @Override public void onMessage(WebSocketMessage msg) { String json = (String) msg.getData(); if (json.isEmpty()) return; Message m = MessageCodec.decode(json); logger.debug("araqne logdb client: received {}", msg.getData()); if (m.getType() == Type.Response) table.signal(m.getRequestId(), m); else if (m.getType() == Type.Trap) { for (TrapListener listener : listeners) { try { listener.onTrap(m); } catch (Throwable t) { logger.error("araqne logdb client: trap listener should not throw any exception", t); } } } } @Override public void onError(Throwable t) { } @Override public void onClose(Throwable t) { for (TrapListener listener : listeners) { try { listener.onClose(t); } catch (Throwable t2) { logger.error("araqne logdb client: trap listener should not throw any exception", t2); } } // close for server side reset try { releaseResources(); } catch (IOException e) { } } @Override public void close() throws IOException { if (isClosed()) return; releaseResources(); } private void releaseResources() throws IOException { super.close(); timer.cancel(); table.close(); websocket.close(); } private class PingTask extends TimerTask { @Override public void run() { if (websocket.isClosed()) return; try { synchronized (sendLock) { // send msgbus ping websocket.sendPing(); // websocket.send("ping"); } } catch (Throwable t) { // ignore ping fail } } } }