All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.net.websocket.WebSocketConnection Maven / Gradle / Ivy

/*
 * RED5 Open Source Flash Server - https://github.com/red5 Copyright 2006-2018 by respective authors (see below). All rights reserved. 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.red5.net.websocket;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.stream.Stream;

import javax.websocket.Session;

import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.websocket.Constants;
import org.apache.tomcat.websocket.WsSession;
import org.red5.server.AttributeStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * WebSocketConnection 
* This class represents a WebSocket connection with a client (browser). * * @see rfc6455 * * @author Paul Gregoire */ public class WebSocketConnection extends AttributeStore { private static final Logger log = LoggerFactory.getLogger(WebSocketConnection.class); private static final boolean isTrace = log.isTraceEnabled(); private static final boolean isDebug = log.isDebugEnabled(); // Sending async on windows times out private static boolean useAsync = !System.getProperty("os.name").contains("Windows"); private static long sendTimeout = 20000L; private AtomicBoolean connected = new AtomicBoolean(false); // associated websocket session private final WsSession wsSession; private String httpSessionId; private String host; private String path; private String origin; private String userAgent = "undefined"; /** * Contains http headers and other web-socket information from the initial request. */ private Map> headers; private Map extensions; /** * Contains uri parameters from the initial request. */ private Map querystringParameters; /** * Connection protocol (ex. chat, json, etc) */ private String protocol; // stats private volatile long readBytes, writtenBytes; // atomic field updaters private final static AtomicLongFieldUpdater readUpdater = AtomicLongFieldUpdater.newUpdater(WebSocketConnection.class, "readBytes"); private final static AtomicLongFieldUpdater writeUpdater = AtomicLongFieldUpdater.newUpdater(WebSocketConnection.class, "writtenBytes"); public WebSocketConnection(WebSocketScope scope, Session session) { // set our path path = scope.getPath(); // cast ws session this.wsSession = (WsSession) session; // set the local session id httpSessionId = Optional.ofNullable(wsSession.getHttpSessionId()).orElse(wsSession.getId()); // get extensions wsSession.getNegotiatedExtensions().forEach(extension -> { if (extensions == null) { extensions = new HashMap<>(); } extensions.put(extension.getName(), extension); }); log.debug("extensions: {}", extensions); // get querystring String queryString = wsSession.getQueryString(); log.debug("queryString: {}", queryString); if (StringUtils.isNotBlank(queryString)) { // bust it up by ampersand String[] qsParams = queryString.split("&"); // loop-thru adding to the local map querystringParameters = new HashMap<>(); Stream.of(qsParams).forEach(qsParam -> { String[] parts = qsParam.split("="); if (parts.length == 2) { querystringParameters.put(parts[0], parts[1]); } else { querystringParameters.put(parts[0], null); } }); } // get request parameters Map pathParameters = wsSession.getPathParameters(); if (isDebug) { log.debug("pathParameters: {}", pathParameters); } // get user props Map userProps = wsSession.getUserProperties(); if (isDebug) { log.debug("userProps: {}", userProps); } } /** * Sends text to the client. * * @param data * string / text data * @throws UnsupportedEncodingException * @throws IOException */ public void send(String data) throws UnsupportedEncodingException, IOException { if (isDebug) { log.debug("send message: {}", data); } // process the incoming string if (StringUtils.isNotBlank(data)) { if (wsSession.isOpen()) { if (isConnected()) { try { int lengthToWrite = data.getBytes().length; if (useAsync) { // a new future is returned on each call Future sendFuture = wsSession.getAsyncRemote().sendText(data); sendFuture.get(sendTimeout, TimeUnit.MILLISECONDS); } else { wsSession.getBasicRemote().sendText(data); } writeUpdater.addAndGet(this, lengthToWrite); } catch (TimeoutException e) { log.warn("Send timed out"); } catch (Exception e) { log.warn("Send text exception", e); } } else { throw new IOException("WS connection closed"); } } else { throw new IOException("WS session closed"); } } else { throw new UnsupportedEncodingException("Cannot send a null string"); } } /** * Sends binary data to the client. * * @param buf * @throws IOException */ public void send(byte[] buf) throws IOException { if (isDebug) { log.debug("send binary: {}", Arrays.toString(buf)); } if (wsSession.isOpen()) { try { // send the bytes if (useAsync) { Future sendFuture = wsSession.getAsyncRemote().sendBinary(ByteBuffer.wrap(buf)); // wait up-to ws timeout sendFuture.get(sendTimeout, TimeUnit.MILLISECONDS); } else { wsSession.getBasicRemote().sendBinary(ByteBuffer.wrap(buf)); } // update counter writeUpdater.addAndGet(this, buf.length); } catch (Exception e) { log.warn("Send bytes exception", e); } } else { throw new IOException("WS session closed"); } } /** * Sends a ping to the client. * * @param buf * @throws IOException * @throws IllegalArgumentException */ public void sendPing(byte[] buf) throws IllegalArgumentException, IOException { if (isTrace) { log.trace("send ping: {}", buf); } if (wsSession.isOpen()) { // send the bytes wsSession.getBasicRemote().sendPing(ByteBuffer.wrap(buf)); // update counter writeUpdater.addAndGet(this, buf.length); } else { throw new IOException("WS session closed"); } } /** * Sends a pong back to the client; normally in response to a ping. * * @param buf * @throws IOException * @throws IllegalArgumentException */ public void sendPong(byte[] buf) throws IllegalArgumentException, IOException { if (isTrace) { log.trace("send pong: {}", buf); } if (wsSession.isOpen()) { // send the bytes wsSession.getBasicRemote().sendPong(ByteBuffer.wrap(buf)); // update counter writeUpdater.addAndGet(this, buf.length); } else { throw new IOException("WS session closed"); } } /** * close Connection */ public void close() { if (connected.compareAndSet(true, false)) { // TODO disconnect from scope etc... // normal close if (wsSession.isOpen()) { try { wsSession.close(); } catch (IOException e) { } } } } public long getReadBytes() { return readBytes; } public void updateReadBytes(long read) { readUpdater.addAndGet(this, read); } public long getWrittenBytes() { return writtenBytes; } /** * @return the connected */ public boolean isConnected() { return connected.get(); } /** * On connected, set flag. */ public void setConnected() { boolean connectSuccess = connected.compareAndSet(false, true); log.debug("Connect success: {}", connectSuccess); } /** * @return the host */ public String getHost() { return String.format("%s://%s%s", (isSecure() ? "wss" : "ws"), host, path); } /** * @param host * the host to set */ public void setHost(String host) { this.host = host; } /** * @return the origin */ public String getOrigin() { return origin; } /** * @param origin * the origin to set */ public void setOrigin(String origin) { this.origin = origin; } /** * Return whether or not the session is secure. * * @return true if secure and false if unsecure or unconnected */ public boolean isSecure() { return (wsSession != null) ? wsSession.isSecure() : false; } public String getPath() { return path; } /** * @param path * the path to set */ public void setPath(String path) { if (path.charAt(path.length() - 1) == '/') { this.path = path.substring(0, path.length() - 1); } else { this.path = path; } } /** * Returns the WsSession id associated with this connection. * * @return sessionId */ public String getSessionId() { return wsSession.getId(); } /** * Sets / overrides this connections HttpSession id. * * @param httpSessionId */ public void setHttpSessionId(String httpSessionId) { this.httpSessionId = httpSessionId; } /** * Returns the HttpSession id associated with this connection. * * @return sessionId */ public String getHttpSessionId() { return httpSessionId; } /** * Returns the user agent. * * @return userAgent */ public String getUserAgent() { return userAgent; } /** * Sets the incoming headers. * * @param headers */ public void setHeaders(Map> headers) { if (headers != null && !headers.isEmpty()) { // look for both upper and lower case List userAgentHeader = Optional.ofNullable(headers.get(WSConstants.HTTP_HEADER_USERAGENT)).orElse(headers.get(WSConstants.HTTP_HEADER_USERAGENT.toLowerCase())); if (userAgentHeader != null && !userAgentHeader.isEmpty()) { userAgent = userAgentHeader.get(0); } List hostHeader = Optional.ofNullable(headers.get(Constants.HOST_HEADER_NAME)).orElse(headers.get(Constants.HOST_HEADER_NAME.toLowerCase())); if (hostHeader != null && !hostHeader.isEmpty()) { host = hostHeader.get(0); } List originHeader = Optional.ofNullable(headers.get(Constants.ORIGIN_HEADER_NAME)).orElse(headers.get(Constants.ORIGIN_HEADER_NAME.toLowerCase())); if (originHeader != null && !originHeader.isEmpty()) { origin = originHeader.get(0); } Optional> protocolHeader = Optional.ofNullable(headers.get(WSConstants.WS_HEADER_PROTOCOL)); if (protocolHeader.isPresent()) { if (isDebug) { log.debug("Protocol header(s) exist: {}", protocolHeader.get()); } protocol = protocolHeader.get().get(0); } if (isDebug) { log.debug("Set from headers - user-agent: {} host: {} origin: {}", userAgent, host, origin); } this.headers = headers; } else { this.headers = Collections.emptyMap(); } } public Map> getHeaders() { return headers; } public Map getQuerystringParameters() { return querystringParameters; } public void setQuerystringParameters(Map querystringParameters) { if (this.querystringParameters == null) { this.querystringParameters = new ConcurrentHashMap<>(); } this.querystringParameters.putAll(querystringParameters); } /** * Returns whether or not extensions are enabled on this connection. * * @return true if extensions are enabled, false otherwise */ public boolean hasExtensions() { return extensions != null && !extensions.isEmpty(); } /** * Returns enabled extensions. * * @return extensions */ public Map getExtensions() { return extensions; } /** * Sets the extensions. * * @param extensions */ public void setExtensions(Map extensions) { this.extensions = extensions; } /** * Returns the extensions list as a comma separated string as specified by the rfc. * * @return extension list string or null if no extensions are enabled */ public String getExtensionsAsString() { String extensionsList = null; if (extensions != null) { StringBuilder sb = new StringBuilder(); for (String key : extensions.keySet()) { sb.append(key); sb.append("; "); } extensionsList = sb.toString().trim(); } return extensionsList; } /** * Returns whether or not a protocol is enabled on this connection. * * @return true if protocol is enabled, false otherwise */ public boolean hasProtocol() { return protocol != null; } /** * Returns the protocol enabled on this connection. * * @return protocol */ public String getProtocol() { return protocol; } /** * Sets the protocol. * * @param protocol */ public void setProtocol(String protocol) { this.protocol = protocol; } public static long getSendTimeout() { return sendTimeout; } public static void setSendTimeout(long sendTimeout) { WebSocketConnection.sendTimeout = sendTimeout; } public void setWsSessionTimeout(long idleTimeout) { wsSession.setMaxIdleTimeout(idleTimeout); } public WsSession getWsSession() { return wsSession; } @Override public String toString() { if (wsSession != null && connected.get()) { return "WebSocketConnection [wsId=" + wsSession.getId() + ", sessionId=" + httpSessionId + ", host=" + host + ", origin=" + origin + ", path=" + path + ", secure=" + isSecure() + ", connected=" + connected + "]"; } if (wsSession == null) { return "WebSocketConnection [wsId=not-set, sessionId=not-set, host=" + host + ", origin=" + origin + ", path=" + path + ", secure=not-set, connected=" + connected + "]"; } return "WebSocketConnection [host=" + host + ", origin=" + origin + ", path=" + path + " connected=false]"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy