Setup

First, we need to setup the environment. This includes: create maven project, edit pom.xml file and build the project for the first time, just to download the library files.

Using Netbeans, create new Maven Project. In my case, I name it JettyWebSocket. Edit pom.xml file. Put jettyVersion properties, then add web-socket-server and web-socket-api as dependency. Here is how the pom.xml file would be:

3. Build with Netbeans. Menu Run | Build Project.

Write the Code

The program will consists of the following main component:

MessagingServer as the main class application MessagingAdapter which will capture event when client open connection, receive data (whether text or binary), close connection, error happen for each client connection UserSession interface to track each user’s session MessagingLogic where we put messaging logics Data models Repository which will handle all data related operations

We will start from the independent classes, in this case, the data models. I hope you are familiar with Java. If I have User class, and the package named id.amrishodiq.jettywebsocket.data , that means the file should be <project-directory>/id/amrishodiq/jettywebsocket/data/User.java.

Class User

package id.amrishodiq.jettywebsocket.data;

* User representation of the messaging platform.

*

*/

public class User {

public String username;

public String password; /*** User representation of the messaging platform. @author amrishodiq*/public class User {public String username;public String password; public User(String username, String password) {

this.username = username;

this.password = password;

}

}

Class Message

package id.amrishodiq.jettywebsocket.data;

* Message representation.

*

*/

public class Message {

public String from;

public String to;

public String body;

public long sent;

} /*** Message representation. @author amrishodiq*/public class Message {public String from;public String to;public String body;public long sent;

Class Data

package id.amrishodiq.jettywebsocket.data;

* Data is a wrapper for transmittable object between client and server.

*

*/

public class Data {

public static final int AUTHENTICATION_LOGIN = 1;



public static final int MESSAGING_SEND = 101;



public int operation;

public User user;

public Message message;

public String session;

} /*** Data is a wrapper for transmittable object between client and server. @author amrishodiq*/public class Data {public static final int AUTHENTICATION_LOGIN = 1;public static final int MESSAGING_SEND = 101;public int operation;public User user;public Message message;public String session;

Class Repository

package id.amrishodiq.jettywebsocket.data; import java.util.HashMap;

* Repository responsible for all operation related to data saving or retrieval.

*

*/

public class Repository {

private static Repository instance;

public static Repository getInstance() {

if (instance == null) {

instance = new Repository();

}

return instance;

}



private final HashMap<String, User> users = new HashMap<>();

public final String DUMMY_SESSION = "SOMEVALIDSESSION";



private Repository() {

initDummyUser();

}



private void initDummyUser() {

User user = new User("alice", "123456");

users.put(user.username, user);

user = new User("bob", "654321");

users.put(user.username, user);

user = new User("charlie", "qwerty");

users.put(user.username, user);

}



public boolean isValid(User user) {

User item = users.get(user.username);

if (item != null && item.password.equals(user.password)) {

return true;

}



return false;

}



public boolean isValid(String session) {

if (DUMMY_SESSION.equals(session)) {

return true;

}

return false;

}

} /*** Repository responsible for all operation related to data saving or retrieval. @author amrishodiq*/public class Repository {private static Repository instance;public static Repository getInstance() {if (instance == null) {instance = new Repository();return instance;private final HashMap users = new HashMap<>();public final String DUMMY_SESSION = "SOMEVALIDSESSION";private Repository() {initDummyUser();private void initDummyUser() {User user = new User("alice", "123456");users.put(user.username, user);user = new User("bob", "654321");users.put(user.username, user);user = new User("charlie", "qwerty");users.put(user.username, user);public boolean isValid(User user) {User item = users.get(user.username);if (item != null && item.password.equals(user.password)) {return true;return false;public boolean isValid(String session) {if (DUMMY_SESSION.equals(session)) {return true;return false;

Enough for data related classes. Now, we move to the main parts.

Interface UserSession

package id.amrishodiq.jettywebsocket; import id.amrishodiq.jettywebsocket.data.User;

* UserSession is an interface to track every client connection.

*

*/

public interface UserSession {

void receiveText(String text) throws Exception;

void setCurrentUser(User user);

void disconnect(int status, String reason);

} /*** UserSession is an interface to track every client connection. @author amrishodiq*/public interface UserSession {void receiveText(String text) throws Exception;void setCurrentUser(User user);void disconnect(int status, String reason);

Class MessagingLogic

package id.amrishodiq.jettywebsocket; import id.amrishodiq.jettywebsocket.data.Repository;

import com.google.gson.Gson;

import id.amrishodiq.jettywebsocket.data.Data;

import id.amrishodiq.jettywebsocket.data.Message;

import id.amrishodiq.jettywebsocket.data.User;

import java.util.HashMap;

* MessagingLogic will be responsible for parsing received data, do

* authentication and forward the messages between users.

*

*/

public class MessagingLogic {



private static MessagingLogic instance;

public static MessagingLogic getInstance() {

if (instance == null) {

instance = new MessagingLogic();

}

return instance;

}



private HashMap<String, UserSession> userSessions = new HashMap<>();

private Gson gson = new Gson();



private MessagingLogic() {

}



public void receiveText(UserSession session, String text) {

try {

receiveData(session, gson.fromJson(text, Data.class));

} catch (Throwable t) {

}

}



private void receiveData(UserSession session, Data data) {

if (data == null) return;



// for all operation except login, do session checking

if (data.operation != Data.AUTHENTICATION_LOGIN) {

if (!validateSession(data.session)) {

session.disconnect(401, "Invalid session");

}

}



switch (data.operation) {

case Data.AUTHENTICATION_LOGIN:

login(session, data.user);

break;

case Data.MESSAGING_SEND:

send(data.message);

break;

default:

session.disconnect(404, "Wrong operation");

}

}



private void login(UserSession session, User user) {

if (user == null) {

session.disconnect(401, "Give username and password!");

}



if (Repository.getInstance().isValid(user)) {

userSessions.put(user.username, session);



session.setCurrentUser(user);



Data data = new Data();

data.operation = Data.AUTHENTICATION_LOGIN;

data.session = Repository.getInstance().DUMMY_SESSION;



try {

session.receiveText(gson.toJson(data));

} catch (Exception ex) {

ex.printStackTrace();

}



} else {

session.disconnect(401, "Wrong username or password!");

}

}



private boolean validateSession(String session) {

return Repository.getInstance().isValid(session);

}



private void send(Message message) {

if (isUserOnline(message.to)) {

System.out.println("User is online, try to send message");

UserSession userSession = userSessions.get(message.to);

try {

userSession.receiveText(gson.toJson(message));

} catch (Exception ex) {

// put to offline message

System.out.println("User is offline");

}

} else {

// todo put to offline message

System.out.println("User is offline");

}

}



private boolean isUserOnline(String username) {

return userSessions.containsKey(username);

}



public void setOffline(String username) {

userSessions.remove(username);

System.err.println("Set "+username+" offline.");

}



} /*** MessagingLogic will be responsible for parsing received data, do* authentication and forward the messages between users. @author amrishodiq*/public class MessagingLogic {private static MessagingLogic instance;public static MessagingLogic getInstance() {if (instance == null) {instance = new MessagingLogic();return instance;private HashMap userSessions = new HashMap<>();private Gson gson = new Gson();private MessagingLogic() {public void receiveText(UserSession session, String text) {try {receiveData(session, gson.fromJson(text, Data.class));} catch (Throwable t) {private void receiveData(UserSession session, Data data) {if (data == null) return;// for all operation except login, do session checkingif (data.operation != Data.AUTHENTICATION_LOGIN) {if (!validateSession(data.session)) {session.disconnect(401, "Invalid session");switch (data.operation) {case Data.AUTHENTICATION_LOGIN:login(session, data.user);break;case Data.MESSAGING_SEND:send(data.message);break;default:session.disconnect(404, "Wrong operation");private void login(UserSession session, User user) {if (user == null) {session.disconnect(401, "Give username and password!");if (Repository.getInstance().isValid(user)) {userSessions.put(user.username, session);session.setCurrentUser(user);Data data = new Data();data.operation = Data.AUTHENTICATION_LOGIN;data.session = Repository.getInstance().DUMMY_SESSION;try {session.receiveText(gson.toJson(data));} catch (Exception ex) {ex.printStackTrace();} else {session.disconnect(401, "Wrong username or password!");private boolean validateSession(String session) {return Repository.getInstance().isValid(session);private void send(Message message) {if (isUserOnline(message.to)) {System.out.println("User is online, try to send message");UserSession userSession = userSessions.get(message.to);try {userSession.receiveText(gson.toJson(message));} catch (Exception ex) {// put to offline messageSystem.out.println("User is offline");} else {// todo put to offline messageSystem.out.println("User is offline");private boolean isUserOnline(String username) {return userSessions.containsKey(username);public void setOffline(String username) {userSessions.remove(username);System.err.println("Set "+username+" offline.");

Class MessagingAdapter

package id.amrishodiq.jettywebsocket; import id.amrishodiq.jettywebsocket.data.User;

import org.eclipse.jetty.websocket.api.Session;

import org.eclipse.jetty.websocket.api.WebSocketAdapter;

* MessagingAdapter responsible to handle connection, receiving data, forward

* the data to

* data from

* delivered to recipient.

*

*/

public class MessagingAdapter extends WebSocketAdapter implements UserSession {



private Session session;

private User currentUser;



@Override

public void onWebSocketConnect(Session session) {

super.onWebSocketConnect(session);



this.session = session;

} /*** MessagingAdapter responsible to handle connection, receiving data, forward* the data to @see id.amrishodiq.jettywebsocket.MessagingLogic and receive* data from @see id.amrishodiq.jettywebsocket.MessagingLogic to be* delivered to recipient. @author amrishodiq*/public class MessagingAdapter extends WebSocketAdapter implements UserSession {private Session session;private User currentUser;public void onWebSocketConnect(Session session) {super.onWebSocketConnect(session);this.session = session; @Override

public void onWebSocketClose(int statusCode, String reason) {

MessagingLogic.getInstance().setOffline(currentUser.username);



this.session = null;



System.err.println("Close connection "+statusCode+", "+reason);



super.onWebSocketClose(statusCode, reason);

} @Override

public void onWebSocketText(String message) {

super.onWebSocketText(message);



MessagingLogic.getInstance().receiveText(this, message);

} @Override

public void receiveText(String text) throws Exception {

if (session != null && session.isOpen()) {

session.getRemote().sendString(text);

}

} @Override

public void setCurrentUser(User user) {

this.currentUser = user;

} @Override

public void disconnect(int status, String reason) {



session.close(status, reason);

disconnect(status, reason);

}



}

Class MessagingServlet

package id.amrishodiq.jettywebsocket; import org.eclipse.jetty.websocket.servlet.WebSocketServlet;

import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

* Well, it's just a servlet declaration.

*

*/

public class MessagingServlet extends WebSocketServlet { /*** Well, it's just a servlet declaration. @author amrishodiq*/public class MessagingServlet extends WebSocketServlet { @Override

public void configure(WebSocketServletFactory factory) {

factory.register(MessagingAdapter.class);

}



}

Class MessagingServer

package id.amrishodiq.jettywebsocket; import org.eclipse.jetty.server.Server;

import org.eclipse.jetty.server.ServerConnector;

import org.eclipse.jetty.servlet.ServletContextHandler;

* The executable main class.

*

*/

public class MessagingServer {



private Server server;



public void setup() {

server = new Server();

ServerConnector connector = new ServerConnector(server);

connector.setPort(8080);

server.addConnector(connector);



ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);

handler.setContextPath("/");

server.setHandler(handler);



handler.addServlet(MessagingServlet.class, "/messaging");

}



public void start() throws Exception {

server.start();

server.dump(System.err);

server.join();

}



public static void main(String args[]) throws Exception {

MessagingServer theServer = new MessagingServer();

theServer.setup();

theServer.start();

}

} /*** The executable main class. @author amrishodiq*/public class MessagingServer {private Server server;public void setup() {server = new Server();ServerConnector connector = new ServerConnector(server);connector.setPort(8080);server.addConnector(connector);ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);handler.setContextPath("/");server.setHandler(handler);handler.addServlet(MessagingServlet.class, "/messaging");public void start() throws Exception {server.start();server.dump(System.err);server.join();public static void main(String args[]) throws Exception {MessagingServer theServer = new MessagingServer();theServer.setup();theServer.start();

MessagingServer is the executable class (see the main method). We can run this class using Netbeans’ right click and Run. However, we need to have an executable .jar file. Currently, we don’t have it. The .jar file we have is not executable yet.

Now, we need to revise our pom.xml in order to make deliverable .jar.

Build the project once again, then you will have an executable .jar file inside <project-directory>/target . In my case the name is JettyWebSocket-1.0.1.jar .

Functional Test

Run java -jar JettyWebSocket-1.0.1.jar from <project-directory>/target and you will have a running web socket server on port 8080.

Test it with some Web Socket client such as Simple Web Socket Client, it’s a Google Chrome extension. Connect to ws://localhost:8080/messaging . Send this text:

{

"operation": 1,

"user": {

"username": "alice",

"password": "123456"

}

}

Server will response with:

{"operation":1,"session":"SOMEVALIDSESSION"}

Those steps are for authenticate into the messaging server. Once you have the session string, used it to send message. Send the following message:

{

"operation": 101,

"session": "SOMEVALIDSESSION",

"message": {"from":"alice","to":"bob","body":"sembarang aja nih biar seru","sent":1234567}

}

MessagingLogic will find whether bob is online or not. If he is, then this message will be forwarder to bob. But unfortunately is not online.

To test when bob is online, you need another web socket client instance. You need to open new Google Chrome window, then open the Simple Web Socket plugin once more. Open the same address, but login with the following data:

{

"operation": 1,

"user": {

"username": "bob",

"password": "654321"

}

}

Then, send message to alice using following data

{

"operation": 101,

"session": "SOMEVALIDSESSION",

"message": {

"from": "bob",

"to": "alice",

"body": "sembarang aja",

"sent": 1234567

}

}

See, at the previous Simple Web Socket plugin alice logged in, she will receive:

{"from":"bob","to":"alice","body":"sembarang aja","sent":1234567}

Happy trying. If you find any problem, just tell me. We could discuss about it.