📜 ⬆️ ⬇️

WebSocket: Implementing a web application using Jetty Web Socket. Part 1

Good afternoon, habrazhitel!

Congratulations to everyone on the Great Programmer's Day! I wish a working code, confident sockets and the most advanced users!

While working on the automation of a concert agency, at some stage of development I needed a notification system. Access to automation occurs through the web application written by me. And, accordingly, instant notifications should come to the user's browser.
')
To accomplish this task, there are three solutions:

I immediately “dismiss” the first decision (I will not explain the reasons, web developers will understand me).

The second solution is much more like it, but it has its drawbacks:

The third solution is exactly what the doctor ordered.

So, WebSocket.

Among the drawbacks of WebSocket, only the one that only supports webkit browsers (they are Google Chrome and Apple Safari) is important.

Let's try to implement a simple chat, as a web application with the basic possibility of full-duplex messaging between the client and the server.

Implementation of the client part

The implementation of WebSocket JavaScript on the client side is simple, we will not spend a lot of time on it. See listing.

var socket = new WebSocket("ws://myserver.com:8081/"); socket.onopen = function () { console.log(" "); }; socket.onclose = function () { console.log (" "); }; socket.onmessage = function (event) { console.log ("   :", event.data); }; 


You can send messages to the server using the send () method:
socket.send(messageString);

Server side implementation

Implementation of the solution on the server looks much more complicated. The network can find several options for implementation, of which the most closer to me were:

Of these, JWebSocket stands out because it is a great framework for working with WebSocket, which is also a reliable standalone server at the same time. JWebSocket requires a separate post. And now we will focus on the simplest version of the solution on the J2EE platform: Jetty.

What we have? Let's take a look at the diagram.

browser, glassfish and jetty interaction scheme

In my case, we have a GlassFish servlet container on port 8080. The browser sends a request to GlassFish [1], which sends a chat page [2] to the browser, which, if desired, connects to the server via port 8081 using the WebSocket protocol [3]. Further there is a full duplex data exchange between the browser and the server [4].

At the time of writing, the latest version of Jetty 8.0.1.v20110908. We download, unpack (I apologize to all developers using Maven), from the distribution we are interested in 6 libraries:

We add these libraries to the project.

Returning to the client side, download it from here (I don’t want to clutter up the post by listing the HTML code). Add the chat.html file to the project's Web Pages. In the deployment descriptor (web.xml) we specify chat.html as "welcome-file".

Now a little about Jetty.

Jetty's embedded server is in the org.eclipse.jetty.server.Server class. And the constructor can contain either the server address or, as in our case, the port number:

Server jetty = new Server(8081);

Next we need to add the necessary handlers to the jetty and run it. Jetty methods start and stop without start () and stop () parameters, respectively. But the handler we need to write our own, create a new class. In order for us to have a handler for handling connections using the WebSocket protocol, we must inherit it from org.eclipse.jetty.websocket.WebSocketHandler:

public class ChatWebSocketHandler extends WebSocketHandler {
…
}

The WebSocketHandler class has one abstract doWebSocketConnect () method. It is actually called when the browser opens a new connection with jetty, and returns the WebSocket object.

We also need to define our WebSocket class, and we will inherit it from the org.eclipse.jetty.websocket.WebSocket.OnTextMessage interface - this interface works with data passing through the WebSocket protocol as with text data. The org.eclipse.jetty.websocket.WebSocket.OnTextMessage interface contains three methods:

It seems to be all simple! Let's take a look at the ChatWebSocketHandler listing.

 import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketHandler; public class ChatWebSocketHandler extends WebSocketHandler { /** *    */ private final Set<ChatWebSocket> webSockets = new CopyOnWriteArraySet<ChatWebSocket>(); /** *       * @param request * @param protocol  (   ws  wss) * @return */ @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { //      //         // throw new Exception(); //   ,       //      return new ChatWebSocket(); } private class ChatWebSocket implements WebSocket.OnTextMessage { /** *   */ private Connection connection; /** *   */ private String userName = null; /** *     */ private final Pattern authCmdPattern = Pattern.compile("^\\/auth ([\\S]+).*"); /** *       */ private final Pattern getUsersCmdPattern = Pattern.compile("^\\/getUsers.*"); /** *      */ private final Pattern helpCmdPattern = Pattern.compile("^\\/help.*"); /** *      * @param connection */ @Override public void onOpen(Connection connection) { //     ChatWebSocket::connection this.connection = connection; //      ChatWebSocketHandler::webSockets webSockets.add(this); } /** *      * @param data */ @Override public void onMessage(String data) { //       data = data.replaceAll("<", "<").replaceAll(">", ">"); //     if (authCmdPattern.matcher(data).matches()) { Matcher matcher = authCmdPattern.matcher(data); matcher.find(); //     userName = matcher.group(1); try { //      ChatWebSocketHandler::webSockets for (ChatWebSocket webSocket : webSockets) { //   ,     webSocket.connection.sendMessage("inf|" + (webSocket.equals(this) ? "  " : ("   <b>" + userName + "</b>"))); } } catch (IOException x) { //          connection.disconnect(); } //       } else if (getUsersCmdPattern.matcher(data).matches()) { String userList = ""; //      ChatWebSocketHandler::webSockets for (ChatWebSocket webSocket : webSockets) { userList += webSocket.userName + ", "; } userList = userList.substring(0, userList.length() - 2); try { //     connection.sendMessage("inf|  : " + userList); } catch (IOException x) { //          connection.disconnect(); } //      } else if (helpCmdPattern.matcher(data).matches()) { String helpMessage = "     " + "       Enter.<br />" + "   :<br />" + "<ul><li><b>/help</b> -    </li>" + "<li><b>/getUsers</b> -    </li>" + "<li><b>/auth <i></i></b> -  </li></ul>"; try { //   connection.sendMessage("inf|" + helpMessage); } catch (IOException x) { //          connection.disconnect(); } //       } else { try { //     if (userName == null) { connection.sendMessage("err|  <br />" + "  <b>/help</b>  "); return; } //      ChatWebSocketHandler::webSockets for (ChatWebSocket webSocket : webSockets) { //       in   //  ,  -  out webSocket.connection.sendMessage((webSocket.equals(this) ? "out|" : ("in|" + userName + "|")) + data); } } catch (IOException x) { //          connection.disconnect(); } } } /** *       * @param closeCode * @param message */ @Override public void onClose(int closeCode, String message) { //      ChatWebSocketHandler::webSockets webSockets.remove(this); } } } 


And so, what we see in ChatWebSocketHandler:

In the class ChatWebSocket we are interested in the onMessage () method. In it, we will implement the client and server data exchange protocol. According to the comments it is clear how it works.

What is the exchange protocol?

The server receives all text messages from the client; three commands can be distinguished from them:

The client receives messages with the following templates from the server:

The rest I will not paint again, - everything is described in the comments.

Now let's move on to the crucial moment - the launch of the jetty server. So, we have a task: when starting a servlet container, launch jetty; before stopping the servlet container - stop jetty. The easiest way to implement this in GlassFish is to write your ContextListener. Let's see why? We create a new class and inherit it from the javax.servlet.ServletContextListener interface. See listing.

 import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; public class ChatServerServletContextListener implements ServletContextListener { /** *   Jetty */ private Server server = null; /** *       * @param event */ @Override public void contextInitialized(ServletContextEvent event) { try { //   Jetty  8081  this.server = new Server(8081); //  ChatWebSocketHandler   Jetty ChatWebSocketHandler chatWebSocketHandler = new ChatWebSocketHandler(); //     WebSocketHandlerContainer chatWebSocketHandler.setHandler(new DefaultHandler()); //     jetty server.setHandler(chatWebSocketHandler); //  Jetty server.start(); } catch (Throwable e) { e.printStackTrace(); } } /** *       * @param event */ @Override public void contextDestroyed(ServletContextEvent event) { //   jetty -  if (server != null) { try { //  Jetty server.stop(); } catch (Exception e) { e.printStackTrace(); } } } } 


The javax.servlet.ServletContextListener interface has two methods that speak for themselves: contextInitialized (), contextDestroyed ().

Now we just have to connect our ContextListener to the deployment descriptor (web.xml). I will cite his listing:

 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>chat.html</welcome-file> </welcome-file-list> <listener> <listener-class>ChatServerServletContextListener</listener-class> </listener> </web-app> 


Well now you can run the project. In the browser, open two windows at once and indulge. Please pay attention to the time, the messages reach almost instantly. Such a speed of work on ajax is almost impossible to get.

image

image

Conclusion

The entire project can be downloaded from the link.
The project is fully compatible with GlassFish 2.x, 3.x and Tomcat not lower than 5.0. The project was created in Netbeans 7.0.1. Inside the project, you can find ant-deploy.xml for development on Ant. Once again, I apologize wildly to developers using Maven.

In the second part of the article I will describe in detail the problems that arise when working with WebSocket, and their solutions.
In the third part, I will describe ways to deal with ddos ​​attacks on Jetty servers.

Thanks for attention. All again with the holiday!

Source: https://habr.com/ru/post/128380/


All Articles