This plugin for Red5 server encapsulates and extends Tomcat in an embedded form. In addition to the the JEE container features, the Tomcat WebSocket implementation is also included; a reference implementation of JSR-365. Whenever possible the Tomcat code has been used directly, where its not possible (mostly due to packaging), we have copied the original sources. For instance, many of the classes in org.red5.net.websocket.server
originate from org.apache.tomcat.websocket.server
; all for Red5 and Tomcat continue to be licensed under APL 2.0.
The only new part of the configuration to support WebSocket, is the addition of the property to enable or disable the WebSocket feature within the tomcat.server
bean.
<property name="websocketEnabled" value="true" />
The first step is to identify and special configuration in-place within your existing webSocketTransport
or webSocketTransportSecure
beans. If you have specified cipherSuites
or protocols
, they will need to be translated over to the Tomcat configuration bean. Once you've taken note of your configuration options, remove the webSocketTransport
or webSocketTransportSecure
beans in your conf/jee-container.xml
file.
The IP addresses and ports identified for ws
and wss
in the conf/jee-container.xml
file are no longer used. The http
and https
configuration in the Tomcat bean are used instead since this version of the WebSocket plugin is integrated with Tomcat itself.
Use this command to build for JDK8 since we've moved to JDK11: mvn clean install -Djava.release.level=8 -Dmaven.compiler.source=1.8 -Dmaven.compiler.target=1.8
Development is based on version 8.5.x of Tomcat.
Websocket plug-in is integrated into the Tomcat plugin as of this latest release. The primary reasoning behind this is the maintenance aspect; Tomcat has a team and we do not. This change also means a move away from Mina for the I/O layer for WebSockets; the previous plugin will continue to live on here.
This plugin is meant to provide websocket functionality for applications running in red5. The code is constructed to comply with rfc6455 and JSR365.
The previous Red5 WebSocket plugin was developed with assistence from Takahiko Toda and Dhruv Chopra.
Update the conf/jee-container.xml
file to suit your needs.
Non-secure - http and ws:
<bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" depends-on="context.loader,warDeployer" lazy-init="true">
<property name="websocketEnabled" value="true" />
<property name="webappFolder" value="${red5.root}/webapps" />
<property name="connectors">
<list>
<bean name="httpConnector" class="org.red5.server.tomcat.TomcatConnector">
<property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
<property name="address" value="${http.host}:${http.port}" />
<property name="redirectPort" value="${https.port}" />
<property name="connectionProperties">
<map>
<entry key="maxHttpHeaderSize" value="${http.max_headers_size}"/>
<entry key="maxKeepAliveRequests" value="${http.max_keep_alive_requests}"/>
<entry key="keepAliveTimout" value="-1"/>
</map>
</property>
</bean>
</list>
</property>
<property name="baseHost">
<bean class="org.apache.catalina.core.StandardHost">
<property name="name" value="${http.host}" />
</bean>
</property>
</bean>
Secure - https and wss:
<bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" depends-on="context.loader" lazy-init="true">
<property name="websocketEnabled" value="true" />
<property name="webappFolder" value="${red5.root}/webapps" />
<property name="connectors">
<list>
<bean name="httpConnector" class="org.red5.server.tomcat.TomcatConnector">
<property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
<property name="address" value="${http.host}:${http.port}" />
<property name="redirectPort" value="${https.port}" />
</bean>
<bean name="httpsConnector" class="org.red5.server.tomcat.TomcatConnector">
<property name="secure" value="true" />
<property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
<property name="address" value="${http.host}:${https.port}" />
<property name="redirectPort" value="${http.port}" />
<property name="connectionProperties">
<map>
<entry key="port" value="${https.port}" />
<entry key="redirectPort" value="${http.port}" />
<entry key="SSLEnabled" value="true" />
<entry key="sslProtocol" value="TLSv1.2" />
<entry key="ciphers" value="TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA" />
<entry key="useServerCipherSuitesOrder" value="true" />
<entry key="keystoreFile" value="${rtmps.keystorefile}" />
<entry key="keystorePass" value="${rtmps.keystorepass}" />
<entry key="truststoreFile" value="${rtmps.truststorefile}" />
<entry key="truststorePass" value="${rtmps.truststorepass}" />
<entry key="clientAuth" value="false" />
<entry key="allowUnsafeLegacyRenegotiation" value="false" />
<entry key="maxHttpHeaderSize" value="${http.max_headers_size}"/>
<entry key="maxKeepAliveRequests" value="${http.max_keep_alive_requests}"/>
<entry key="keepAliveTimout" value="-1"/>
<entry key="useExecutor" value="true"/>
<entry key="maxThreads" value="${http.max_threads}"/>
<entry key="acceptorThreadCount" value="${http.acceptor_thread_count}"/>
<entry key="processorCache" value="${http.processor_cache}"/>
</map>
</property>
</bean>
</list>
</property>
<property name="baseHost">
<bean class="org.apache.catalina.core.StandardHost">
<property name="name" value="${http.host}" />
</bean>
</property>
</bean>
To bind to more than one IP address / port, add additional httpConnector
or httpsConnector
entries:
<property name="connectors">
<list>
<bean name="httpConnector" class="org.red5.server.tomcat.TomcatConnector">
<property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
<property name="address" value="${http.host}:${http.port}" />
<property name="redirectPort" value="${https.port}" />
</bean>
<bean name="httpConnector1" class="org.red5.server.tomcat.TomcatConnector">
<property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
<property name="address" value="192.168.1.1:5080" />
<property name="redirectPort" value="${https.port}" />
</bean>
<bean name="httpConnector2" class="org.red5.server.tomcat.TomcatConnector">
<property name="protocol" value="org.apache.coyote.http11.Http11Nio2Protocol" />
<property name="address" value="10.10.10.1:5080" />
<property name="redirectPort" value="${https.port}" />
</bean>
</list>
</property>
Note
If you are not using unlimited strength JCE (ex. you are outside the USA), your cipher suite selections will fail if any containing AES_256
are specified.
To enable websocket support in your application, add this to your appStart() method:
WebSocketScopeManager manager = ((WebSocketPlugin) PluginRegistry.getPlugin(WebSocketPlugin.NAME)).getManager(scope);
manager.setApplication(this);
For clean-up add this to appStop():
WebSocketScopeManager manager = ((WebSocketPlugin) PluginRegistry.getPlugin(WebSocketPlugin.NAME)).getManager(scope);
manager.stop();
Lastly, the websocket filter must be added to each web application that will act as a websocket end point. In the webapp descriptor webapps/myapp/WEB-INF/web.xml
add this entry alongside any other filters or servlets.
<!-- WebSocket filter -->
<filter>
<filter-name>WebSocketFilter</filter-name>
<filter-class>org.red5.net.websocket.server.WsFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>WebSocketFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
To support subprotocols, add them as a comma-delimited string in the web.xml
:
<!-- WebSocket subprotocols -->
<context-param>
<param-name>subProtocols</param-name>
<param-value>chat,json</param-value>
</context-param>
The plugin will default to allowing any requested subprotocol if none are specified.
Implementers may extend the default websocket endpoint class provided by this plugin org.red5.net.websocket.server.DefaultWebSocketEndpoint
. The first step is to become familiar with the class and then extend
it in your application; once that is complete, your class must be placed in the lib
directory of your Red5 server, not the webapps/yourapp/WEB-INF/lib
directory. Lastly, in your webapp descriptor webapps/yourapp/WEB-INF/web.xml
file, an entry named wsEndpointClass
will need to be made for your class:
<context-param>
<param-name>wsEndpointClass</param-name>
<param-value>com.mydomain.websocket.MyWebSocketEndpoint</param-value>
</context-param>
One reason to extend the endpoint for your own use is because the default endpoint implementation only handles text data.
Since WebSockets don't implement Same Origin Policy (SOP) nor Cross-Origin Resource Sharing (CORS), we've implemented a means to restrict access via configuration using SOP / CORS logic. To configure the security features, edit your conf/jee-container.xml
file and locate the bean displayed below:
<bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" depends-on="context.loader" lazy-init="true">
<property name="websocketEnabled" value="true" />
<property name="sameOriginPolicy" value="false" />
<property name="crossOriginPolicy" value="true" />
<property name="allowedOrigins">
<array>
<value>localhost</value>
<value>red5.org</value>
</array>
</property>
Properties:
- sameOriginPolicy - Enables or disables SOP. The logic differs from standard web SOP by NOT enforcing protocol and port.
- crossOriginPolicy - Enables or disables CORS. This option pairs with the
allowedOrigins
array. - allowedOrigins - The list or host names or fqdn which are to be permitted access. The default if none are specified is
*
which equates to any or all.
Replace the wsUri variable with your applications path.
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://192.168.1.174:5080/myapp/";
var output; function init() { output = document.getElementById("output"); testWebSocket(); } function testWebSocket() { websocket = new WebSocket(wsUri); websocket.onopen = function(evt) { onOpen(evt) }; websocket.onclose = function(evt) { onClose(evt) }; websocket.onmessage = function(evt) { onMessage(evt) }; websocket.onerror = function(evt) { onError(evt) }; } function onOpen(evt) { writeToScreen("CONNECTED"); doSend("WebSocket rocks"); } function onClose(evt) { writeToScreen("DISCONNECTED"); } function onMessage(evt) { writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>'); websocket.close(); } function onError(evt) { writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data); } function doSend(message) { writeToScreen("SENT: " + message); websocket.send(message); } function writeToScreen(message) { var pre = document.createElement("p"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.appendChild(pre); } window.addEventListener("load", init, false); </script> <h2>WebSocket Test</h2> <div id="output"></div>
https://github.com/Red5/red5-websocket-chat
You can find compiled artifacts via Maven