Синопсис
В предыдущем посте мы создали Jetty Server который стартует прямо с main метода. В этом посте добавим к jetty серверу веб сокеты.
Готовый проект можно взять с gitHub по ссылке: https://github.com/dev-blogs/JettyWebSocket
Что такое веб сокеты
Когда jetty сервак запущен, в принципе возникнуть вопроса как к нему коннектиться не должно, по умолчанию это http протокол. Но http протокол имеет у http протокола есть известный недостаток в который в этом посте мы вдаваться не будем, скажу лишь, что для того чтобы обеспечить взаимодействия клиента с сервером по средствам протокола http, клиент и сервер каждый раз должны заново инициировать соединение, причем соединение только в одну сторону, если клиент ожидает что-то получить от сервера, то приходится опять устанавливать соединение. Поэтому протокол http еще называют stateless протоколом, это означает, что он не сохраняет своего последнего (и вообще никакого) состояния. Для того, чтобы присутствовала иллюзия того, что такое состояние сохраняется, когда вы взаимодействуйте с каким-нибудь веб ресурсом, на пример с веб почтой, используя протокол http, в этом случае применяются разного рода костыли, на пример токены.
В отличие от http протокола соединение клиента и сервера по средством веб сокетов осуществляется двунаправленно, то есть между клиентом и сервером устанавливается канал, через который они могут общаться в реальном времени.
Структура проекта
Структура проекта такая же как и в предыдущем посте отличие будет в добавлении дополнительных классов, которые отвечают за создание веб сокетов:
JettWebSocket ├──src │ ├─main │ │ └─java │ │ └─our │ │ └─task │ │ ├─JettyServer │ │ │ └─JettyStarter.java │ │ └─JettyWebSocket │ │ ├─MySocket.java │ │ └─SocketHandler.java │ └─test │ └─java │ └─our │ └─task │ ├─JettyServer │ └─JettyWebSocket └──pom.xml
Java классы
JettyStarter.java
package our.task.JettyServer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; import our.task.JettyWebSocket.SocketHandler; public class JettyStarter { public static void main(String [] args) { Server server = new Server(); ServerConnector connector = new ServerConnector(server); connector.setPort(8080); server.addConnector(connector); // add first handler ResourceHandler resource_handler = new ResourceHandler(); resource_handler.setDirectoriesListed(true); resource_handler.setWelcomeFiles(new String[]{ "index.html" }); resource_handler.setResourceBase("."); HandlerList handlers = new HandlerList(); // first element is webSocket handler, second element is first handler handlers.setHandlers(new Handler[] {new SocketHandler(), resource_handler}); server.setHandler(handlers); try { server.start(); server.join(); } catch (Throwable t) { t.printStackTrace(System.err); } } }
В классе JettyStarter.java изменения и добавления подсвечены.
SocketHandler.java
package our.task.JettyWebSocket; import org.eclipse.jetty.websocket.server.WebSocketHandler; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; public class SocketHandler extends WebSocketHandler { @Override public void configure(WebSocketServletFactory factory) { factory.register(MySocket.class); } }
MySocket.java
package our.task.JettyWebSocket; import java.io.IOException; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @WebSocket public class MySocket { private Session session; @OnWebSocketConnect public void onConnect(Session session) { System.out.println("Connect: " + session.getRemoteAddress().getAddress()); try { this.session = session; session.getRemote().sendString("Got your connect message"); } catch (IOException e) { e.printStackTrace(); } } @OnWebSocketMessage public void onText(String message) { System.out.println("text: " + message); try { this.session.getRemote().sendString(message); } catch (Exception e) { e.printStackTrace(); } } @OnWebSocketClose public void onClose(int statusCode, String reason) { System.out.println("Close: statusCode=" + statusCode + ", reason=" + reason); } }
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>our.task</groupId> <artifactId>JettyWebSocket</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>JettyWebSocket</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- Server, ServerConnector --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.1.3.v20140225</version> </dependency> <!-- WebSocketHandler, WebSocketServletFactory --> <dependency> <groupId>org.eclipse.jetty.websocket</groupId> <artifactId>websocket-server</artifactId> <version>9.1.3.v20140225</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.3.2</version> </plugin> </plugins> </build> </project>
Запуск проекта
После того как все классы добавлены, попробуем запустить проект. Сначала выполним команду mvn install:
mvn install
После этого мавен подтянет все плагины и зависимости:
[INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building JettyWebSocket 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ JettyWebSocket --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory c:\practice\test1\WebSocketJetty\src\main\resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ JettyWebSocket --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 3 source files to c:\practice\test1\WebSocketJetty\target\classes [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ JettyWebSocket --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory c:\practice\test1\WebSocketJetty\src\test\resources [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ JettyWebSocket --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ JettyWebSocket --- [INFO] No tests to run. [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ JettyWebSocket --- [INFO] Building jar: c:\practice\test1\WebSocketJetty\target\JettyWebSocket-0.0.1-SNAPSHOT.jar [INFO] [INFO] --- maven-install-plugin:2.4:install (default-install) @ JettyWebSocket --- [INFO] Installing c:\practice\test1\WebSocketJetty\target\JettyWebSocket-0.0.1-SNAPSHOT.jar to C:\Users\zheka\.m2\repository\our\task\JettyWebSocket\0.0.1-SNAPSHOT\JettyWebSocket-0.0.1-SNAPSHOT.jar [INFO] Installing c:\practice\test1\WebSocketJetty\pom.xml to C:\Users\zheka\.m2\repository\our\task\JettyWebSocket\0.0.1-SNAPSHOT\JettyWebSocket-0.0.1-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.878 s [INFO] Finished at: 2014-10-16T20:21:16+03:00 [INFO] Final Memory: 14M/156M [INFO] ------------------------------------------------------------------------
mvn exec:java -Dexec.mainClass="our.task.JettyServer.JettyStarter" [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building JettyWebSocket 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- exec-maven-plugin:1.3.2:java (default-cli) @ JettyWebSocket --- WARNING Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6. Connect: /0:0:0:0:0:0:0:1 text: test text: test1 text: test2 Close: statusCode=1005, reason=null
Client 1
<button type="button" onclick="WebSocketTest()">Send</button> <script type="text/javascript"> function WebSocketTest() { alert("WebSocket is supported by your Browser!"); // Let us open a web socket var ws = new WebSocket("ws://localhost:8080"); ws.onopen = function() { // Web Socket is connected, send data using send() ws.send("Message to send"); alert("Message is sent..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("Message is received..."); }; ws.onclose = function() { // websocket is closed. alert("Connection is closed..."); }; } </script>
Client 2
<html> <head> <meta charset=UTF-8> <title>Tomcat WebSocket Chat</title> <script> var ws = new WebSocket("ws://localhost:8080"); ws.onopen = function(){ }; ws.onmessage = function(message){ document.getElementById("chatlog").textContent += message.data + "\n"; }; function postToServer(){ ws.send(document.getElementById("msg").value); document.getElementById("msg").value = ""; } function closeConnect(){ ws.close(); } </script> </head> <body> <textarea id="chatlog" readonly></textarea><br/> <input id="msg" type="text" /> <button type="submit" id="sendButton" onClick="postToServer()">Send!</button> <button type="submit" id="sendButton" onClick="closeConnect()">End</button> </body> </html>
Ссылки
WebSocket in Jetty
WebSockets — полноценный асинхронный веб
Отличный материал!
И «Embedded jetty server (часть 1)» тоже отличный. Все собралось и заработало, несмотря на то, что пришлось немного погуглить по ошибкам мавена. Ошибки были из-за того, что я юзаю Idea и там пришлось вручную вписывать в «Command line» параметр «exec:java».
У меня почему-то сразу закрываются коннекции «Connection is closed». Но это не важно. Ваш материал очень помог в разбирательстве основ Jetty Embedded.
Спасибо!