Java tutorial
/* * 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.net.InetSocketAddress; import java.net.URI; import java.security.Principal; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpointConfig; import javax.websocket.ContainerProvider; import javax.websocket.DeploymentException; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.Extension; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.WebSocketContainer; import org.apache.catalina.LifecycleException; import org.apache.commons.lang3.RandomStringUtils; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter.NextFilter; import org.apache.mina.core.future.WriteFuture; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.apache.mina.filter.codec.ProtocolEncoderOutput; import org.apache.tomcat.websocket.WsRemoteEndpointImplBase; import org.apache.tomcat.websocket.WsSession; import org.apache.tomcat.websocket.WsWebSocketContainer; import org.junit.Test; import org.red5.server.adapter.MultiThreadedApplicationAdapter; import org.red5.server.api.scope.IScope; import org.red5.server.plugin.PluginRegistry; import org.red5.server.scope.GlobalScope; import org.red5.server.tomcat.EmbeddedTomcat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tests for websocket operations. * * @author Paul Gregoire (mondain@gmail.com) */ public class WebSocketServerTest { protected static Logger log = LoggerFactory.getLogger(WebSocketServerTest.class); private static Object writtenResult; private static WebSocketScope scope; /* * Test data from the rfc <pre> A single-frame unmasked text message (contains "Hello") 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f A single-frame masked text message (contains "Hello") 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 A fragmented unmasked text message 0x01 0x03 0x48 0x65 0x6c (contains "Hel") 0x80 0x02 0x6c 0x6f (contains "lo") Unmasked Ping request and masked Ping response 0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains a body of "Hello", but the contents of the body are arbitrary) 0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains a body of "Hello", matching the body of the ping) A 256 bytes binary message in a single unmasked frame 0x82 0x7E 0x0100 [256 bytes of binary data] A 64KiB binary message in a single unmasked frame 0x82 0x7F 0x0000000000010000 [65536 bytes of binary data] </pre> */ @SuppressWarnings("unused") @Test public void testMultiThreaded() throws Throwable { log.info("testMultiThreaded enter"); // create the server instance Thread server = new Thread() { @Override public void run() { log.debug("Server thread run"); try { WSServer.main(null); } catch (Exception e) { log.error("Error in server thread", e); } log.debug("Server thread exit"); } }; server.setDaemon(true); server.start(); // add plugin to the registry WebSocketPlugin plugin = new WebSocketPlugin(); PluginRegistry.register(plugin); // start plugin plugin.doStart(); // create a scope for the manager IScope appScope = new GlobalScope(); // create an app MultiThreadedApplicationAdapter app = new MultiThreadedApplicationAdapter(); app.setScope(appScope); // add the app plugin.setApplication(app); // get the manager WebSocketScopeManager manager = plugin.getManager(appScope); manager.setApplication(appScope); // wait for server while (!WSServer.isListening()) { Thread.sleep(10L); } // how many threads int threads = 1; List<Worker> tasks = new ArrayList<Worker>(threads); for (int t = 0; t < threads; t++) { tasks.add(new Worker()); } ExecutorService executorService = Executors.newFixedThreadPool(threads); // invokeAll() blocks until all tasks have run... long start = System.nanoTime(); List<Future<Object>> futures = executorService.invokeAll(tasks); log.info("Runtime: {} ns", (System.nanoTime() - start)); for (Worker r : tasks) { // loop through and check results } Thread.sleep(2000L); // stop server server.interrupt(); WSServer.stop(); // stop plugin PluginRegistry.shutdown(); log.info("testMultiThreaded exit"); } // @Test // public void testDecodingErrorJuneSixth() throws Throwable { // log.info("-------------------------------------------------------test66 enter"); // // masked // IoBuffer in = IoBuffer.wrap(new byte[] { (byte) 0x81, (byte) 0xFE, (byte) 0x00, (byte) 0xAE, (byte) 0x97, (byte) 0x6A, (byte) 0xAD, (byte) 0x23, (byte) 0xEC, (byte) 0x48, (byte) 0xC9, (byte) 0x42, (byte) 0xE3, (byte) 0x0B, (byte) 0x8F, (byte) 0x19, (byte) 0xEC, (byte) 0x48, (byte) 0xDE, (byte) 0x46, // (byte) 0xE4, (byte) 0x19, (byte) 0xC4, (byte) 0x4C, (byte) 0xF9, (byte) 0x03, (byte) 0xC9, (byte) 0x01, (byte) 0xAD, (byte) 0x48, (byte) 0x9F, (byte) 0x12, (byte) 0xA3, (byte) 0x5A, (byte) 0x98, (byte) 0x16, (byte) 0xA4, (byte) 0x5E, (byte) 0x9E, (byte) 0x1A, (byte) 0xAE, (byte) 0x5C, (byte) 0x9A, // (byte) 0x10, (byte) 0xA4, (byte) 0x5E, (byte) 0x9E, (byte) 0x01, (byte) 0xBB, (byte) 0x48, (byte) 0xD8, (byte) 0x50, (byte) 0xF2, (byte) 0x18, (byte) 0xC4, (byte) 0x47, (byte) 0xB5, (byte) 0x50, (byte) 0x8F, (byte) 0x4B, (byte) 0xE0, (byte) 0x03, (byte) 0xDF, (byte) 0x4D, (byte) 0xA4, (byte) 0x5B, // (byte) 0x9D, (byte) 0x4F, (byte) 0xA3, (byte) 0x5F, (byte) 0x9F, (byte) 0x46, (byte) 0xA6, (byte) 0x53, (byte) 0xDF, (byte) 0x10, (byte) 0xFE, (byte) 0x09, (byte) 0xDC, (byte) 0x01, (byte) 0xBB, (byte) 0x48, (byte) 0xDE, (byte) 0x46, (byte) 0xE4, (byte) 0x19, (byte) 0xC4, (byte) 0x4C, (byte) 0xF9, // (byte) 0x48, (byte) 0x97, (byte) 0x58, (byte) 0xB5, (byte) 0x0E, (byte) 0xCC, (byte) 0x57, (byte) 0xF6, (byte) 0x48, (byte) 0x97, (byte) 0x57, (byte) 0xE5, (byte) 0x1F, (byte) 0xC8, (byte) 0x5E, (byte) 0xBB, (byte) 0x48, (byte) 0xC8, (byte) 0x5B, (byte) 0xE3, (byte) 0x18, (byte) 0xCC, (byte) 0x01, // (byte) 0xAD, (byte) 0x11, (byte) 0x8F, (byte) 0x56, (byte) 0xE4, (byte) 0x0F, (byte) 0xDF, (byte) 0x4D, (byte) 0xF6, (byte) 0x07, (byte) 0xC8, (byte) 0x01, (byte) 0xAD, (byte) 0x48, (byte) 0xFD, (byte) 0x42 }); // // get results // WSMessage result = WebSocketDecoder.decodeIncommingData(in, null); // assertTrue(result.getMessageType() == MessageType.TEXT); // log.info("{}", result.getMessageAsString()); // assertEquals("Hello", result.getMessageAsString()); // log.info("-------------------------------------------------------test66 exit"); // } @Test public void testMasked() throws Throwable { log.info("testMasked enter"); // masked IoBuffer in = IoBuffer.wrap(new byte[] { (byte) 0x81, (byte) 0x85, (byte) 0x37, (byte) 0xfa, (byte) 0x21, (byte) 0x3d, (byte) 0x7f, (byte) 0x9f, (byte) 0x4d, (byte) 0x51, (byte) 0x58 }); // create session and conn DummySession sess = new DummySession(); WebSocketConnection conn = new WebSocketConnection(scope, sess); //session.setAttribute(Constants.CONNECTION, conn); // decode // DummyDecoder decoder = new DummyDecoder(); // decoder.dummyDecode(session, in, new DummyOutput()); // assertTrue(((WSMessage) writtenResult).getMessageType() == WSMessage.MessageType.TEXT); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); // log.info("testMasked exit"); } @Test public void testUnmasked() throws Throwable { log.info("testUnmasked enter"); // unmasked IoBuffer in = IoBuffer.wrap(new byte[] { (byte) 0x81, (byte) 0x05, (byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f }); // create session and conn DummySession sess = new DummySession(); WebSocketConnection conn = new WebSocketConnection(scope, sess); //session.setAttribute(Constants.CONNECTION, conn); // decode // DummyDecoder decoder = new DummyDecoder(); // decoder.dummyDecode(session, in, new DummyOutput()); // assertTrue(((WSMessage) writtenResult).getMessageType() == WSMessage.MessageType.TEXT); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); // log.info("testUnmasked exit"); } @Test public void testFragmented() throws Throwable { log.info("testFragmented enter"); // fragments byte[] part1 = new byte[] { (byte) 0x01, (byte) 0x03, (byte) 0x48, (byte) 0x65, (byte) 0x6c }; byte[] part2 = new byte[] { (byte) 0x80, (byte) 0x02, (byte) 0x6c, (byte) 0x6f }; // create session and conn DummySession sess = new DummySession(); WebSocketConnection conn = new WebSocketConnection(scope, sess); //session.setAttribute(Constants.CONNECTION, conn); // decode // DummyDecoder decoder = new DummyDecoder(); // DummyOutput out = new DummyOutput(); // // create io buffer // IoBuffer in = IoBuffer.allocate(5, false); // // add part 1 // in.put(part1); // in.flip(); // // decode with first fragment // decoder.dummyDecode(session, in, out); // // add part 2 // in = IoBuffer.allocate(4, false); // in.put(part2); // in.flip(); // // decode with second fragment // decoder.dummyDecode(session, in, out); // // check result // assertTrue(((WSMessage) writtenResult).getMessageType() == WSMessage.MessageType.TEXT); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); log.info("testFragmented exit"); } // @Test // public void testUnmaskedPing() throws Throwable { // log.info("testUnmaskedPing enter"); // // unmasked ping // IoBuffer in = IoBuffer.wrap(new byte[] { (byte) 0x89, (byte) 0x05, (byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f }); // // create session and conn // DummySession session = new DummySession(); // WebSocketConnection conn = new WebSocketConnection(session); // session.setAttribute(Constants.CONNECTION, conn); // // decode // DummyDecoder decoder = new DummyDecoder(); // decoder.dummyDecode(session, in, new DummyOutput()); // assertTrue(((WSMessage) writtenResult).getMessageType() == MessageType.PING); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); // log.info("testUnmaskedPing exit"); // } // @Test // public void testMaskedPong() throws Throwable { // log.info("testMaskedPong enter"); // // masked pong // IoBuffer in = IoBuffer.wrap(new byte[] { (byte) 0x8a, (byte) 0x85, (byte) 0x37, (byte) 0xfa, (byte) 0x21, (byte) 0x3d, (byte) 0x7f, (byte) 0x9f, (byte) 0x4d, (byte) 0x51, (byte) 0x58 }); // // create session and conn // DummySession session = new DummySession(); // WebSocketConnection conn = new WebSocketConnection(session); // session.setAttribute(Constants.CONNECTION, conn); // // decode // DummyDecoder decoder = new DummyDecoder(); // decoder.dummyDecode(session, in, new DummyOutput()); // assertTrue(((WSMessage) writtenResult).getMessageType() == MessageType.PONG); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); // log.info("testMaskedPong exit"); // } // @Test // public void testUnmaskedRoundTrip() throws Throwable { // log.info("testUnmaskedRoundTrip enter"); // // create session and conn // DummySession session = new DummySession(); // WebSocketConnection conn = new WebSocketConnection(session); // session.setAttribute(Constants.CONNECTION, conn); // // encode // DummyEncoder encoder = new DummyEncoder(); // encoder.dummyEncode(session, Packet.build("Hello".getBytes(), WSMessage.MessageType.TEXT), new DummyOutput()); // // decode // DummyDecoder decoder = new DummyDecoder(); // decoder.dummyDecode(session, (IoBuffer) writtenResult, new DummyOutput()); // assertTrue(((WSMessage) writtenResult).getMessageType() == WSMessage.MessageType.TEXT); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); // log.info("testUnmaskedRoundTrip exit"); // } // @Test // public void testUnmaskedPingRoundTrip() throws Throwable { // log.info("testUnmaskedPingRoundTrip enter"); // // create session and conn // DummySession session = new DummySession(); // WebSocketConnection conn = new WebSocketConnection(session); // session.setAttribute(Constants.CONNECTION, conn); // // encode // DummyEncoder encoder = new DummyEncoder(); // encoder.dummyEncode(session, Packet.build("Hello".getBytes(), MessageType.PING), new DummyOutput()); // // decode // DummyDecoder decoder = new DummyDecoder(); // decoder.dummyDecode(session, (IoBuffer) writtenResult, new DummyOutput()); // assertTrue(((WSMessage) writtenResult).getMessageType() == MessageType.PING); // assertEquals("Hello", ((WSMessage) writtenResult).getMessageAsString()); // log.info("testUnmaskedPingRoundTrip exit"); // } @Test public void testUriWithParams() throws Throwable { log.info("\ntestUriWithParams enter"); // create the server instance Thread server = new Thread() { @Override public void run() { log.debug("Server thread run"); try { WSServer.main(null); } catch (Exception e) { log.error("Error in server thread", e); } log.debug("Server thread exit"); } }; server.setDaemon(true); server.start(); // add plugin to the registry WebSocketPlugin plugin = new WebSocketPlugin(); PluginRegistry.register(plugin); // start plugin plugin.doStart(); // create a scope for the manager IScope appScope = new GlobalScope(); // create an app MultiThreadedApplicationAdapter app = new MultiThreadedApplicationAdapter(); app.setScope(appScope); // add the app plugin.setApplication(app); // get the manager WebSocketScopeManager manager = plugin.getManager(appScope); manager.setApplication(appScope); // wait for server while (!WSServer.isListening()) { Thread.sleep(10L); } // create the client final TyrusWSClient client = new TyrusWSClient(); //final TyrusWSClient client = new TyrusWSClient(8192 * 10); Thread t = new Thread(new Runnable() { public void run() { client.start(); } }, "tyrus"); t.start(); t.join(5000); // send a message //client.sendMessage("This is a test"); // terminate client client.terminate(); // stop server server.interrupt(); WSServer.stop(); // stop plugin PluginRegistry.shutdown(); log.info("testUriWithParams exit"); } private class Worker implements Callable<Object> { boolean failed; public Object call() throws Exception { // WSClient client = new WSClient("localhost", 8888); // //WSClient client = new WSClient("localhost", 8888, 8192 * 10); // client.connect(); // if (client.isConnected()) { // client.send("This is a test: " + System.currentTimeMillis()); // } else { // failed = true; // } return failed; } } public static class WSServer { private static EmbeddedTomcat tomcat; private static boolean listening; public static void stop() { try { tomcat.stop(); } catch (LifecycleException e) { e.printStackTrace(); } listening = false; } public static boolean isListening() { return listening; } public static void main(String[] args) throws IOException, LifecycleException { // loop through the addresses and bind Set<InetSocketAddress> socketAddresses = new HashSet<InetSocketAddress>(); socketAddresses.add(new InetSocketAddress("0.0.0.0", 8888)); //socketAddresses.add(new InetSocketAddress("localhost", 8888)); log.debug("Binding to {}", socketAddresses.toString()); tomcat.start(); System.out.println("WS server started listening"); listening = true; while (true) { try { Thread.sleep(2000L); } catch (InterruptedException e) { System.out.println("WS server stopped listening"); } } } } @ClientEndpoint public class TyrusWSClient extends Endpoint { private WebSocketContainer container = null; private Session session = null; private Object waitLock = new Object(); private String cookie = null; public TyrusWSClient() { } public TyrusWSClient(int cookieLength) { this.cookie = RandomStringUtils.randomAscii(cookieLength); log.debug("Cookie length: {}", cookie.length()); } @Override public void onOpen(Session session, EndpointConfig config) { log.debug("Opened: {} config: {}", session, config); } @OnMessage public void onMessage(String message) { log.debug("Received msg: {}", message); } public void sendMessage(String message) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } private void wait4TerminateSignal() { synchronized (waitLock) { try { waitLock.wait(); } catch (InterruptedException e) { } } } public void terminate() { synchronized (waitLock) { waitLock.notifyAll(); } } // https://tyrus-project.github.io/ public void start() { //ClientManager mgr = ClientManager.createClient(); //org.glassfish.tyrus.client.ClientManager //mgr.connectToServer(TyrusWSClient.class, "ws://localhost:8888/app?id=cafebeef0123"); try { // Tyrus is plugged via ServiceLoader API. See notes above container = ContainerProvider.getWebSocketContainer(); if (cookie != null) { ClientEndpointConfig cec = ClientEndpointConfig.Builder.create() .configurator(new ClientEndpointConfig.Configurator() { @Override public void beforeRequest(Map<String, List<String>> headers) { super.beforeRequest(headers); List<String> cookieList = headers.get("Cookie"); if (null == cookieList) { cookieList = new ArrayList<>(); } cookieList.add(String.format("monster=\"%s\"", cookie)); // set your cookie value here headers.put("Cookie", cookieList); } }).build(); session = container.connectToServer(TyrusWSClient.class, cec, URI.create("ws://localhost:8888/default?id=cafebeef0123")); } else { session = container.connectToServer(TyrusWSClient.class, URI.create("ws://localhost:8888/default?id=cafebeef0123")); } wait4TerminateSignal(); } catch (Exception e) { e.printStackTrace(); } finally { if (session != null) { try { session.close(); } catch (Exception e) { e.printStackTrace(); } } } log.debug("exit"); } } private class DummyOutput implements ProtocolDecoderOutput, ProtocolEncoderOutput { @Override public void mergeAll() { } @Override public WriteFuture flush() { return null; } @Override public void write(Object message) { log.debug("out: {}", message); WebSocketServerTest.writtenResult = message; } @Override public void flush(NextFilter nextFilter, IoSession session) { } } private class DummySession extends WsSession { //localEndpoint, wsRemoteEndpoint, wsWebSocketContainer, requestUri, requestParameterMap, queryString, userPrincipal, httpSessionId, negotiatedExtensions, subProtocol, pathParameters, secure, endpointConfig; public DummySession() throws DeploymentException { this(null, null, null, null, null, null, null, RandomStringUtils.randomAlphanumeric(8), null, null, null, false, null); } public DummySession(Endpoint localEndpoint, WsRemoteEndpointImplBase wsRemoteEndpoint, WsWebSocketContainer wsWebSocketContainer, URI requestUri, Map<String, List<String>> requestParameterMap, String queryString, Principal userPrincipal, String httpSessionId, List<Extension> negotiatedExtensions, String subProtocol, Map<String, String> pathParameters, boolean secure, EndpointConfig endpointConfig) throws DeploymentException { super(localEndpoint, wsRemoteEndpoint, wsWebSocketContainer, requestUri, requestParameterMap, queryString, userPrincipal, httpSessionId, negotiatedExtensions, subProtocol, pathParameters, secure, endpointConfig); } } }