打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
hasCode.com ? Blog Archive ? Creating a Chat Application using Java EE 7, Websockets and GlassFis
Java EE 7 is out now and so I was curious to play around with the new specifications and APIs from in this technology stack.
That’s why I didn’t hesitate to add yet another websocket-chat tutorial to the existing ones on the internet in favor of gathering some experience with this technology and a possible integration using a GlassFish 4 server, the new Java API for JSON Processing for data serialization combined with custom websocket encoders/decoders and finally adding some Bootstrap and jQuery on the client side.
Contents
The final chat application.
Prerequisites
We need the following stuff to build and run the following application:
Java 7 JDK
Maven 3
Creating the new Project
You should simply use the Java EE 7 Webapp Archetype here so that your final pom.xml could look similar to this one (shortened):
<properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency></dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.6</version> [..] </plugin> </plugins></build>
Maven Embedded GlassFish Plugin
Adding the following snippet allows us to quickly download and startup a full GlassFish 4 instance with our application deployed by running one simple Maven goal.
Big thanks @resah for pointing out at one issue that got me initially (EMBEDDED_GLASSFISH-142) but is no problem if you’re using the following plugin configuration
<plugin> <groupId>org.glassfish.embedded</groupId> <artifactId>maven-embedded-glassfish-plugin</artifactId> <version>4.0</version> <configuration> <goalPrefix>embedded-glassfish</goalPrefix> <app>${basedir}/target/${project.artifactId}-${project.version}.war</app> <autoDelete>true</autoDelete> <port>8080</port> <name>${project.artifactId}</name> <contextRoot>hascode</contextRoot> </configuration> <executions> <execution> <goals> <goal>deploy</goal> </goals> </execution> </executions></plugin>
Afterwards we’re able to startup the application server by running the following command:
mvn embedded-glassfish:run
The Websocket Endpoint
This is our endpoint implementation. Some short facts here about the implementation.
@ServerEndpoint defines a new endpoint – the value specifies the URL path and allows PathParams as we’re used know from JAX-RS.
So value=”/chat/{room}” allows the client to open a websocket connection to an URL like ws://<IP>:<PORT>/<CONTEXT_PATH>/chat/room .. e.g. ws://0.0.0.0:8080/hascode/chat/java
The value in curly braces may be injected as a path parameter in the lifecycle callback methods in the endpoint using javax.websocket.server.PathParam
In addition to the URL schema, we’re specifying an encoder and a decoder class. We need this because we’re using a custom DTO object to pass our chat data between server and client
When a client opens the first connection to the server, he passes the chat-room he wants to enter as a path-parameter and we’re storing this value in the user properties map using session.getUserProperties()..
Now when a chat participant posts a new message over the tcp connection to the server, we’re iterating over all open session, and each session that is assigned to the room the session/message is bound to, receives the decoded and re-encoded message
If we wanted to send a simple text or binary message we could have used session.getBasicRemote().sendBinary() or session.getBasicRemote().sendText()…
package com.hascode.tutorial; import java.io.IOException;import java.util.logging.Level;import java.util.logging.Logger; import javax.websocket.EncodeException;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/chat/{room}", encoders = ChatMessageEncoder.class, decoders = ChatMessageDecoder.class)public class ChatEndpoint { private final Logger log = Logger.getLogger(getClass().getName());  @OnOpen public void open(final Session session, @PathParam("room") final String room) { log.info("session openend and bound to room: " + room); session.getUserProperties().put("room", room); }  @OnMessage public void onMessage(final Session session, final ChatMessage chatMessage) { String room = (String) session.getUserProperties().get("room"); try { for (Session s : session.getOpenSessions()) { if (s.isOpen() && room.equals(s.getUserProperties().get("room"))) { s.getBasicRemote().sendObject(chatMessage); } } } catch (IOException | EncodeException e) { log.log(Level.WARNING, "onMessage failed", e); } }}
ChatMessage Data-Transfer-Object
This is the DTO we’re using to transfer the chat data bidirectional over the TCP connection.
package com.hascode.tutorial; import java.util.Date; public class ChatMessage { private String message; private String sender; private Date received;  // getter, setter, toString omitted..}
Converting ChatMessages
These are the encoder and the decoder implementations that are referenced in the @ServerEndpoint properties and that allow us to convert incoming or outgoing data between ChatMessages and the JSON format.
Decoder Implementation
The following decoder converts incoming chat message strings to out ChatMessage POJO.
We’re using the newJava API for JSON Processing (JSR353) here to pass data from the JSON format to the ChatMessage.
The willDecode() method allows us to specify if our decoder is able to handle the incoming data .. in our case this is always true
package com.hascode.tutorial; import java.io.StringReader;import java.util.Date; import javax.json.Json;import javax.json.JsonObject;import javax.websocket.DecodeException;import javax.websocket.Decoder;import javax.websocket.EndpointConfig; public class ChatMessageDecoder implements Decoder.Text<ChatMessage> { @Override public void init(final EndpointConfig config) { }  @Override public void destroy() { }  @Override public ChatMessage decode(final String textMessage) throws DecodeException { ChatMessage chatMessage = new ChatMessage(); JsonObject obj = Json.createReader(new StringReader(textMessage)) .readObject(); chatMessage.setMessage(obj.getString("message")); chatMessage.setSender(obj.getString("sender")); chatMessage.setReceived(new Date()); return chatMessage; }  @Override public boolean willDecode(final String s) { return true; }}
Encoder Implementation
This encoder does the opposite – converting a ChatMessage to a JSON string again using the Java API for JSON Processing and its shiny new JSON builder.
package com.hascode.tutorial; import javax.json.Json;import javax.websocket.EncodeException;import javax.websocket.Encoder;import javax.websocket.EndpointConfig; public class ChatMessageEncoder implements Encoder.Text<ChatMessage> { @Override public void init(final EndpointConfig config) { }  @Override public void destroy() { }  @Override public String encode(final ChatMessage chatMessage) throws EncodeException { return Json.createObjectBuilder() .add("message", chatMessage.getMessage()) .add("sender", chatMessage.getSender()) .add("received", chatMessage.getReceived().toString()).build() .toString(); }}
Bootstrap, Javascript .. the Client Side of Life…
Finally we’re adding the following index.html to the src/main/webapp directory (shortened, please feel free to have a look a the tutorial sources for the full code)
There are only three interesting API calls that we should remember here – the rest of the javascript adds some functions to the user interface etc …
A websocket URL follows this schema: ws://IP:PORT/CONTEXT_PATH/ENDPOINT_URL e.g ws://0.0.0.0:8080/hascode/chat/java
A new websocket connection is created using the native WebSocket object e.g. var wsocket = new WebSocket(‘ws://0.0.0.0:8080/hascode/chat/java’);
Registering a callback function to receive incoming messages from the server goes like this: wsocket.onmessage = yourCallbackFunction;
Sending a message to the server is done by wsocket.send() .. pass a string, binary data .. whatever you like ..
Closing a connection is simply done by wsocket.close()
There is a lot of useful information that I did not add to this tutorial from keepalive-pings to the handshake protocol and other features .. one good starting point for detailed information about websockets might beIETF RFC 6455
<!DOCTYPE html><html lang="en"><head>[..]<script> var wsocket; var serviceLocation = "ws://0.0.0.0:8080/hascode/chat/"; var $nickName; var $message; var $chatWindow; var room = '';  function onMessageReceived(evt) { //var msg = eval('(' + evt.data + ')'); var msg = JSON.parse(evt.data); // native API var $messageLine = $('<tr><td class="received">' + msg.received + '</td><td class="user label label-info">' + msg.sender + '</td><td class="message badge">' + msg.message + '</td></tr>'); $chatWindow.append($messageLine); } function sendMessage() { var msg = '{"message":"' + $message.val() + '", "sender":"' + $nickName.val() + '", "received":""}'; wsocket.send(msg); $message.val('').focus(); }  function connectToChatserver() { room = $('#chatroom option:selected').val(); wsocket = new WebSocket(serviceLocation + room); wsocket.onmessage = onMessageReceived; }  function leaveRoom() { wsocket.close(); $chatWindow.empty(); $('.chat-wrapper').hide(); $('.chat-signin').show(); $nickName.focus(); }  $(document).ready(function() { $nickName = $('#nickname'); $message = $('#message'); $chatWindow = $('#response'); $('.chat-wrapper').hide(); $nickName.focus();  $('#enterRoom').click(function(evt) { evt.preventDefault(); connectToChatserver(); $('.chat-wrapper h2').text('Chat # '+$nickName.val() + "@" + room); $('.chat-signin').hide(); $('.chat-wrapper').show(); $message.focus(); }); $('#do-chat').submit(function(evt) { evt.preventDefault(); sendMessage() });  $('#leave-room').click(function(){ leaveRoom(); }); });</script></head> <body>  <div class="container chat-signin"> <form class="form-signin"> <h2 class="form-signin-heading">Chat sign in</h2> <label for="nickname">Nickname</label> <input type="text" class="input-block-level" placeholder="Nickname" id="nickname"> <div class="btn-group"> <label for="chatroom">Chatroom</label> <select size="1" id="chatroom"> <option>arduino</option> <option>java</option> <option>groovy</option> <option>scala</option> </select> </div> <button class="btn btn-large btn-primary" type="submit" id="enterRoom">Sign in</button> </form> </div> <!-- /container -->  <div class="container chat-wrapper"> <form id="do-chat"> <h2 class="alert alert-success"></h2> <table id="response" class="table table-bordered"></table> <fieldset> <legend>Enter your message..</legend> <div class="controls"> <input type="text" class="input-block-level" placeholder="Your message..." id="message" style="height:60px"/> <input type="submit" class="btn btn-large btn-block btn-primary" value="Send message" /> <button class="btn btn-large btn-block" type="button" id="leave-room">Leave room</button> </div> </fieldset> </form> </div></body></html>
Build, Deploy, Run…
Now we’re ready to build and deploy our application. If you’re using the embedded-glassfish-plugin your simply need to run
mvn package embedded-glassfish:run
Alternatively upload the application to your GlassFish instance and use hascode as context path (or modify the path in the index.html according to your context path!).
Finally you should be able to load the chat application at the following URL: http://localhost:8080/hascode and it should look similar to this screenshot:
The final chat application.
Screencast
This is our chat in action – I’ve used two browser instances – one Chrome, one Firefox here to simulate two chat participants …
Tutorial Sources
Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/javaee7-websocket-chat.git
Direct Download
For those of you who do not want to build the application by themselves, I’ve added the war file as a direct download here:
https://bitbucket.org/hascode/javaee7-websocket-chat/downloads/javaee7-websocket-chat-1.0.0.war
Slides
I’ve added the following presentation on my slideshare account:
Resources
JSR 342: JavaTM Platform, Enterprise Edition 7 (Java EE 7) Specification
Oracle.com: The Java EE 7 Tutorial
GlassFish Project Website
Bootstrap Project Website
Java API for JSON Processing (JSR353)
Internet Engineering Task Force: RFC 6455 – Websockets
Tags:bootstrap,encoder,glassfish,html5,Java,javaee,javascript,json,json-p,websockets
This entry was posted on Tuesday, August 13th, 2013 at 9:16 pm and is filed underEnterprise,Java. You can follow any responses to this entry through theRSS 2.0 feed. You can skip to the end and leave a response. Pinging is currently not allowed.
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
JavaEE7+Websockets+GlassFish4打造聊天室
WEB消息提醒实现之二 实现方式-websocket实现方式
基于django channel 实现websocket的聊天室
068.侦测档案是否包含宏
Come/WebSocket? Introducing the Atmosphere framework
Qt for Embedded Linux and DirectFB
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服