diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java index 0befe392b4e3..e1dc73495669 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java @@ -36,6 +36,8 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.eclipse.jetty.websocket.api.Session; @@ -43,9 +45,6 @@ import com.google.gson.Gson; import com.sun.net.httpserver.HttpServer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - /** * * ConsoleProxy, singleton class that manages overall activities in console proxy process. To make legacy code work, we still @@ -82,7 +81,7 @@ public class ConsoleProxy { static boolean standaloneStart = false; static String encryptorPassword = "Dummy"; - static final String[] skipProperties = new String[]{"certificate", "cacertificate", "keystore_password", "privatekey"}; + static final String[] skipProperties = new String[] {"certificate", "cacertificate", "keystore_password", "privatekey"}; static Set allowedSessions = new HashSet<>(); @@ -93,11 +92,13 @@ public static void addAllowedSession(String sessionUuid) { private static void configLog4j() { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); URL configUrl = loader.getResource("/conf/log4j-cloud.xml"); - if (configUrl == null) + if (configUrl == null) { configUrl = ClassLoader.getSystemResource("log4j-cloud.xml"); + } - if (configUrl == null) + if (configUrl == null) { configUrl = ClassLoader.getSystemResource("conf/log4j-cloud.xml"); + } if (configUrl != null) { try { @@ -114,7 +115,6 @@ private static void configLog4j() { } catch (URISyntaxException e) { System.out.println("Unable to convert log4j configuration Url to URI"); } - // DOMConfigurator.configure(configUrl); } else { System.out.println("Configure log4j with default properties"); } @@ -122,20 +122,21 @@ private static void configLog4j() { private static void configProxy(Properties conf) { LOGGER.info("Configure console proxy..."); - for (Object key : conf.keySet()) { - LOGGER.info("Property " + (String)key + ": " + conf.getProperty((String)key)); - if (!ArrayUtils.contains(skipProperties, key)) { - LOGGER.info("Property " + (String)key + ": " + conf.getProperty((String)key)); + if (conf != null) { + for (Object key : conf.keySet()) { + if (!ArrayUtils.contains(skipProperties, key)) { + LOGGER.info("Property " + (String) key + ": " + conf.getProperty((String) key)); + } } } - String s = conf.getProperty("consoleproxy.httpListenPort"); + String s = conf != null ? conf.getProperty("consoleproxy.httpListenPort") : null; if (s != null) { httpListenPort = Integer.parseInt(s); LOGGER.info("Setting httpListenPort=" + s); } - s = conf.getProperty("premium"); + s = conf != null ? conf.getProperty("premium") : null; if (s != null && s.equalsIgnoreCase("true")) { LOGGER.info("Premium setting will override settings from consoleproxy.properties, listen at port 443"); httpListenPort = 443; @@ -144,25 +145,25 @@ private static void configProxy(Properties conf) { factoryClzName = ConsoleProxyBaseServerFactoryImpl.class.getName(); } - s = conf.getProperty("consoleproxy.httpCmdListenPort"); + s = conf != null ? conf.getProperty("consoleproxy.httpCmdListenPort") : null; if (s != null) { httpCmdListenPort = Integer.parseInt(s); LOGGER.info("Setting httpCmdListenPort=" + s); } - s = conf.getProperty("consoleproxy.reconnectMaxRetry"); + s = conf != null ? conf.getProperty("consoleproxy.reconnectMaxRetry") : null; if (s != null) { reconnectMaxRetry = Integer.parseInt(s); LOGGER.info("Setting reconnectMaxRetry=" + reconnectMaxRetry); } - s = conf.getProperty("consoleproxy.readTimeoutSeconds"); + s = conf != null ? conf.getProperty("consoleproxy.readTimeoutSeconds") : null; if (s != null) { readTimeoutSeconds = Integer.parseInt(s); LOGGER.info("Setting readTimeoutSeconds=" + readTimeoutSeconds); } - s = conf.getProperty("consoleproxy.defaultBufferSize"); + s = conf != null ? conf.getProperty("consoleproxy.defaultBufferSize") : null; if (s != null) { defaultBufferSize = Integer.parseInt(s); LOGGER.info("Setting defaultBufferSize=" + defaultBufferSize); @@ -173,7 +174,7 @@ public static ConsoleProxyServerFactory getHttpServerFactory() { try { Class clz = Class.forName(factoryClzName); try { - ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance(); + ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory) clz.newInstance(); factory.init(ConsoleProxy.ksBits, ConsoleProxy.ksPassword); return factory; } catch (InstantiationException e) { @@ -197,11 +198,11 @@ public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(Console authResult.setHost(param.getClientHostAddress()); authResult.setPort(param.getClientHostPort()); - if (org.apache.commons.lang3.StringUtils.isNotBlank(param.getExtraSecurityToken())) { + if (StringUtils.isNotBlank(param.getExtraSecurityToken())) { String extraToken = param.getExtraSecurityToken(); String clientProvidedToken = param.getClientProvidedExtraSecurityToken(); - LOGGER.debug(String.format("Extra security validation for the console access, provided %s " + - "to validate against %s", clientProvidedToken, extraToken)); + LOGGER.debug(String.format("Extra security validation for the console access, provided %s to validate against %s", + clientProvidedToken, extraToken)); if (!extraToken.equals(clientProvidedToken)) { LOGGER.error("The provided extra token does not match the expected value for this console endpoint"); @@ -233,20 +234,21 @@ public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(Console Object result; try { result = - authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), param.getClientTag(), - param.getClientHostPassword(), param.getTicket(), reauthentication, param.getSessionUuid()); + authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), + param.getClientTag(), param.getClientHostPassword(), param.getTicket(), reauthentication, + param.getSessionUuid(), param.getClientIp()); } catch (IllegalAccessException e) { - LOGGER.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e); + LOGGER.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException for vm: " + param.getClientTag(), e); authResult.setSuccess(false); return authResult; } catch (InvocationTargetException e) { - LOGGER.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + param.getClientTag(), e); + LOGGER.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException for vm: " + param.getClientTag(), e); authResult.setSuccess(false); return authResult; } if (result != null && result instanceof String) { - authResult = new Gson().fromJson((String)result, ConsoleProxyAuthenticationResult.class); + authResult = new Gson().fromJson((String) result, ConsoleProxyAuthenticationResult.class); } else { LOGGER.error("Invalid authentication return object " + result + " for vm: " + param.getClientTag() + ", decline the access"); authResult.setSuccess(false); @@ -286,7 +288,8 @@ public static void ensureRoute(String address) { } } - public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword, String password, Boolean isSourceIpCheckEnabled) { + public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword, + String password, Boolean isSourceIpCheckEnabled) { setEncryptorPassword(password); configLog4j(); LOGGER.info("Start console proxy with context"); @@ -308,7 +311,7 @@ public static void startWithContext(Properties conf, Object context, byte[] ksBi final ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class contextClazz = loader.loadClass("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource"); authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, - String.class, String.class, String.class, Boolean.class, String.class); + String.class, String.class, String.class, Boolean.class, String.class, String.class); reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class); ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class); } catch (SecurityException e) { @@ -326,34 +329,40 @@ public static void startWithContext(Properties conf, Object context, byte[] ksBi Properties props = new Properties(); if (confs == null) { final File file = PropertiesUtil.findConfigFile("consoleproxy.properties"); - if (file == null) + if (file == null) { LOGGER.info("Can't load consoleproxy.properties from classpath, will use default configuration"); - else + } else { try { confs = new FileInputStream(file); } catch (FileNotFoundException e) { LOGGER.info("Ignoring file not found exception and using defaults"); } + } } if (confs != null) { try { props.load(confs); + if (conf == null) { + conf = new Properties(); + } for (Object key : props.keySet()) { // give properties passed via context high priority, treat properties from consoleproxy.properties // as default values - if (conf.get(key) == null) + if (conf.get(key) == null) { conf.put(key, props.get(key)); + } } } catch (Exception e) { LOGGER.error(e.toString(), e); + } finally { + try { + confs.close(); + } catch (IOException e) { + LOGGER.error("Failed to close consoleproxy.properties : " + e.toString(), e); + } } } - try { - confs.close(); - } catch (IOException e) { - LOGGER.error("Failed to close consolepropxy.properties : " + e.toString(), e); - } start(conf); } @@ -474,8 +483,8 @@ public static ConsoleProxyClient getVncViewer(ConsoleProxyClientParam param) thr LOGGER.info("The rfb thread died, reinitializing the viewer " + viewer); viewer.initClient(param); } else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) { - LOGGER.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() + ", sid in request: " + - param.getClientHostPassword()); + LOGGER.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() + + ", sid in request: " + param.getClientHostPassword()); viewer.initClient(param); } } @@ -484,8 +493,9 @@ public static ConsoleProxyClient getVncViewer(ConsoleProxyClientParam param) thr ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); String loadInfo = statsCollector.getStatsReport(); reportLoadInfo(loadInfo); - if (LOGGER.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Report load change : " + loadInfo); + } } return viewer; @@ -509,13 +519,15 @@ public static ConsoleProxyClient getAjaxVncViewer(ConsoleProxyClientParam param, // protected against malicious attack by modifying URL content if (ajaxSession != null) { long ajaxSessionIdFromUrl = Long.parseLong(ajaxSession); - if (ajaxSessionIdFromUrl != viewer.getAjaxSessionId()) + if (ajaxSessionIdFromUrl != viewer.getAjaxSessionId()) { throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": modified AJAX session id"); + } } - if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() || - !param.getClientHostPassword().equals(viewer.getClientHostPassword())) + if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() + || !param.getClientHostPassword().equals(viewer.getClientHostPassword())) { throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); + } if (!viewer.isFrontEndAlive()) { @@ -529,8 +541,9 @@ public static ConsoleProxyClient getAjaxVncViewer(ConsoleProxyClientParam param, ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); String loadInfo = statsCollector.getStatsReport(); reportLoadInfo(loadInfo); - if (LOGGER.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Report load change : " + loadInfo); + } } return viewer; } @@ -566,9 +579,11 @@ public static void authenticationExternally(ConsoleProxyClientParam param) throw ConsoleProxyAuthenticationResult authResult = authenticateConsoleAccess(param, false); if (authResult == null || !authResult.isSuccess()) { - LOGGER.warn("External authenticator failed authentication request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword()); + LOGGER.warn("External authenticator failed authentication request for vm " + param.getClientTag() + + " with sid " + param.getClientHostPassword()); - throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword()); + throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + + " with sid " + param.getClientHostPassword()); } } @@ -596,50 +611,55 @@ public void execute(Runnable r) { } public static ConsoleProxyNoVncClient getNoVncViewer(ConsoleProxyClientParam param, String ajaxSession, - Session session) throws AuthenticationException { + Session session) throws AuthenticationException { boolean reportLoadChange = false; String clientKey = param.getClientMapKey(); - synchronized (connectionMap) { - ConsoleProxyClient viewer = connectionMap.get(clientKey); - if (viewer == null || viewer.getClass() != ConsoleProxyNoVncClient.class) { - authenticationExternally(param); - viewer = new ConsoleProxyNoVncClient(session); - viewer.initClient(param); + LOGGER.debug("Getting NoVNC viewer for {}. Client tag: {}. session UUID: {}", + clientKey, param.getClientTag(), param.getSessionUuid()); +synchronized (connectionMap) { + ConsoleProxyClient viewer = connectionMap.get(clientKey); + if (viewer == null || viewer.getClass() != ConsoleProxyNoVncClient.class) { + authenticationExternally(param); + viewer = new ConsoleProxyNoVncClient(session); + viewer.initClient(param); + + connectionMap.put(clientKey, viewer); + reportLoadChange = true; + } else { + if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() + || !param.getClientHostPassword().equals(viewer.getClientHostPassword())) { + throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); + } - connectionMap.put(clientKey, viewer); - reportLoadChange = true; - } else { - if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() || - !param.getClientHostPassword().equals(viewer.getClientHostPassword())) - throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); + try { + authenticationExternally(param); + } catch (Exception e) { + LOGGER.error("Authentication failed for param: " + param); + return null; + } + LOGGER.info("Initializing new novnc client and disconnecting existing session"); + try { + ((ConsoleProxyNoVncClient) viewer).getSession().disconnect(); + } catch (IOException e) { + LOGGER.error("Exception while disconnect session of novnc viewer object: " + viewer, e); + } + removeViewer(viewer); + viewer = new ConsoleProxyNoVncClient(session); + viewer.initClient(param); + connectionMap.put(clientKey, viewer); + reportLoadChange = true; + } - try { - authenticationExternally(param); - } catch (Exception e) { - LOGGER.error("Authentication failed for param: " + param); - return null; - } - LOGGER.info("Initializing new novnc client and disconnecting existing session"); - try { - ((ConsoleProxyNoVncClient)viewer).getSession().disconnect(); - } catch (IOException e) { - LOGGER.error("Exception while disconnect session of novnc viewer object: " + viewer, e); - } - removeViewer(viewer); - viewer = new ConsoleProxyNoVncClient(session); - viewer.initClient(param); - connectionMap.put(clientKey, viewer); - reportLoadChange = true; - } if (reportLoadChange) { ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); String loadInfo = statsCollector.getStatsReport(); reportLoadInfo(loadInfo); - if (LOGGER.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Report load change : " + loadInfo); + } } - return (ConsoleProxyNoVncClient)viewer; + return (ConsoleProxyNoVncClient) viewer; } } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java index 0e8f576cf6db..08fa63403b3a 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java @@ -32,9 +32,13 @@ * management software */ public class ConsoleProxyGCThread extends Thread { - protected Logger logger = LogManager.getLogger(ConsoleProxyGCThread.class); + private static final Logger logger = LogManager.getLogger(ConsoleProxyGCThread.class); - private final static int MAX_SESSION_IDLE_SECONDS = 180; + /** + * Maximum time (in seconds) a console session is allowed to be idle before it is closed. + * This value should be kept in sync with ConsoleProxy.VIEWER_LINGER_SECONDS. + */ + private static final int MAX_SESSION_IDLE_SECONDS = 180; private final Map connMap; private final Set removedSessionsSet; @@ -46,21 +50,21 @@ public ConsoleProxyGCThread(Map connMap, Set } private void cleanupLogging() { - if (lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) + if (lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) { return; + } lastLogScan = System.currentTimeMillis(); File logDir = new File("./logs"); - File files[] = logDir.listFiles(); + File[] files = logDir.listFiles(); if (files != null) { for (File file : files) { if (System.currentTimeMillis() - file.lastModified() >= 86400000L) { try { file.delete(); } catch (Throwable e) { - logger.info("[ignored]" - + "failed to delete file: " + e.getLocalizedMessage()); + logger.info("[ignored] failed to delete file: " + e.getLocalizedMessage()); } } } @@ -78,10 +82,10 @@ public void run() { bReportLoad = false; if (logger.isDebugEnabled()) { - logger.debug(String.format("connMap=%s, removedSessions=%s", connMap, removedSessionsSet)); + logger.debug(String.format("ConsoleProxyGCThread loop: connMap=%s, removedSessions=%s", connMap, removedSessionsSet)); } - Set e = connMap.keySet(); - Iterator iterator = e.iterator(); + Set keys = connMap.keySet(); + Iterator iterator = keys.iterator(); while (iterator.hasNext()) { String key; ConsoleProxyClient client; @@ -91,8 +95,12 @@ public void run() { client = connMap.get(key); } - long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000; - if (seconds_unused < MAX_SESSION_IDLE_SECONDS) { + if (client == null) { + continue; + } + + long secondsUnused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000; + if (secondsUnused < MAX_SESSION_IDLE_SECONDS) { continue; } @@ -102,12 +110,12 @@ public void run() { } // close the server connection - logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds"); + logger.info("Dropping " + client + " which has not been used for " + secondsUnused + " seconds"); client.closeClient(); } if (bReportLoad || System.currentTimeMillis() - lastReportTick > 5000) { - // report load changes + // report load changes, including removed sessions since last report ConsoleProxyClientStatsCollector collector = new ConsoleProxyClientStatsCollector(connMap); collector.setRemovedSessions(new ArrayList<>(removedSessionsSet)); String loadInfo = collector.getStatsReport(); @@ -125,7 +133,7 @@ public void run() { try { Thread.sleep(5000); } catch (InterruptedException ex) { - logger.debug("[ignored] Console proxy was interrupted during GC."); + logger.debug("[ignored] Console proxy GC thread interrupted.", ex); } } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java index a9639d0b32e3..c0172fe816c2 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java @@ -40,8 +40,9 @@ @WebSocket public class ConsoleProxyNoVNCHandler extends WebSocketHandler { + private static final Logger logger = LogManager.getLogger(ConsoleProxyNoVNCHandler.class); + private ConsoleProxyNoVncClient viewer = null; - protected Logger logger = LogManager.getLogger(getClass()); public ConsoleProxyNoVNCHandler() { super(); @@ -93,15 +94,16 @@ public void onConnect(final Session session) throws IOException, InterruptedExce String websocketUrl = queryMap.get("websocketUrl"); String sessionUuid = queryMap.get("sessionUuid"); String clientIp = session.getRemoteAddress().getAddress().getHostAddress(); + boolean sessionRequiresNewViewer = Boolean.parseBoolean(queryMap.get("sessionRequiresNewViewer")); - if (tag == null) + if (tag == null) { tag = ""; + } - long ajaxSessionId = 0; int port; - - if (host == null || portStr == null || sid == null) - throw new IllegalArgumentException(); + if (host == null || portStr == null || sid == null) { + throw new IllegalArgumentException("Missing required console connection parameters"); + } try { port = Integer.parseInt(portStr); @@ -112,7 +114,7 @@ public void onConnect(final Session session) throws IOException, InterruptedExce if (ajaxSessionIdStr != null) { try { - ajaxSessionId = Long.parseLong(ajaxSessionIdStr); + Long.parseLong(ajaxSessionIdStr); } catch (NumberFormatException e) { logger.error("Invalid ajaxSessionId (sess) value in query string: {}. Expected a number.", ajaxSessionIdStr, e); throw new IllegalArgumentException(e); @@ -148,10 +150,11 @@ public void onConnect(final Session session) throws IOException, InterruptedExce if (queryMap.containsKey("extra")) { param.setClientProvidedExtraSecurityToken(queryMap.get("extra")); } + viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session); logger.info("Viewer has been created successfully [session UUID: {}, client IP: {}].", sessionUuid, clientIp); } catch (Exception e) { - logger.error("Failed to create viewer [session UUID: {}, client IP: {}] due to {}.", sessionUuid, clientIp, e.getMessage(), e); + logger.error("Failed to create viewer [session UUID: {}, client IP: {}].", sessionUuid, clientIp, e); return; } finally { if (viewer == null) { @@ -160,7 +163,7 @@ public void onConnect(final Session session) throws IOException, InterruptedExce } } - private boolean checkSessionSourceIp(final Session session, final String sourceIP, String sessionSourceIP) throws IOException { + private boolean checkSessionSourceIp(final Session session, final String sourceIP, final String sessionSourceIP) throws IOException { logger.info("Verifying session source IP {} from WebSocket connection request.", sessionSourceIP); if (ConsoleProxy.isSourceIpCheckEnabled && (sessionSourceIP == null || !sessionSourceIP.equals(sourceIP))) { logger.warn("Failed to access console as the source IP to request the console is {}.", sourceIP); @@ -174,7 +177,7 @@ private boolean checkSessionSourceIp(final Session session, final String sourceI @OnWebSocketClose public void onClose(Session session, int statusCode, String reason) throws IOException, InterruptedException { String sessionSourceIp = session.getRemoteAddress().getAddress().getHostAddress(); - logger.debug("Closing WebSocket session [source IP: {}, status code: {}].", sessionSourceIp, statusCode); + logger.debug("Closing WebSocket session [source IP: {}, status code: {}, reason: {}].", sessionSourceIp, statusCode, reason); if (viewer != null) { ConsoleProxy.removeViewer(viewer); } @@ -183,12 +186,20 @@ public void onClose(Session session, int statusCode, String reason) throws IOExc @OnWebSocketFrame public void onFrame(Frame f) throws IOException { + if (viewer == null) { + logger.debug("Ignoring WebSocket frame because viewer is not initialized yet."); + return; + } logger.trace("Sending client [ID: {}] frame of {} bytes.", viewer.getClientId(), f.getPayloadLength()); viewer.sendClientFrame(f); } @OnWebSocketError public void onError(Throwable cause) { - logger.error("Error on WebSocket [client ID: {}, session UUID: {}].", cause, viewer.getClientId(), viewer.getSessionUuid()); + if (viewer != null) { + logger.error("Error on WebSocket [client ID: {}, session UUID: {}].", viewer.getClientId(), viewer.getSessionUuid(), cause); + } else { + logger.error("Error on WebSocket before viewer initialization.", cause); + } } }