This article describes how to run WebSockets on sap AS Java 7.4.
First of all, couple of words, why do we need WebSockets at all.
0. Brief introduction.
If you need real time client-server communication (e.g. chat, collaborative text editor etc) for today you
have several capabilities:
1. Comet (long-held HTTP request allows a web server to push data to a browser, without the browser explicitly requesting it) https://en.wikipedia.org/wiki/Comet_(programming)
Main disadvantage of such an approach - AS needs to maintain vast amounts of open http connections. And more abstractly - we are using
http connections in a radically different way compared they should be used.
2. Ajax polling. (client requests information from server periodically). Disadvantages: hight load without "real" real time eventing.
3. WebSocket (protocol providing full-duplex communication channels over a single TCP connection) https://en.wikipedia.org/wiki/WebSocket
WebSocket is part of JSR 356 and part of JEE7 specification, and as far as I googled, SAP supports WebSockets for abap stack
or in hana beta version. And SAP AS Java 7.40 runs on JEE5.
So we need to "teach" AS to understand WebSocket protocol. In this article I will describe how to do this with Jetty. https://en.wikipedia.org/wiki/Jetty_(web_server)
Other intresting approaches: JWebSocket,node.js,tomcat 7
1. How to install Embedded Jetty into SAP AS Java.
We don't have Servlet 3.0 in Java AS 7.4, so we will have to download 7 jetty versio, this jars from repository:
For latter jetty versions you will have to backport Servlet 3.0 to portal 7.4 and this task doesn't look like simple :smile:
You can use this link to find thise jars http://www.eclipse.org/jetty/download.html
2. Write WebSocketHandler.
You need to extend WebSocketHandler abstract class and override some of it's methods (doWebSocketConnect,WebSocket.OnTextMessage,
onOpen, onMessage, onClose etc). Here is example code:
package ;
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;
import com.sap.tc.logging.Location;
public class ChatWebSocketHandler extends WebSocketHandler {
private Location logger = Location.getLocation(ChatWebSocketHandler.class);
/**
* Active web sockets set
*/
private final Set<ChatWebSocket> webSockets = new CopyOnWriteArraySet<ChatWebSocket>();
/**
* Executing on new connetcion establishment
*
* @param request
* @param protocol
* protocol (it can be ws and wss)
* @return
*/
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
logger.debugT("ChatWebSocketHandler doWebSocketConnect protocol: "+protocol);
// We can refuse client and throw exception
// throw new Exception();
// or we can return an object that will contain an abstraction
// of client-server connetcion and will handle
// client requests
return new ChatWebSocket();
}
private class ChatWebSocket implements WebSocket.OnTextMessage {
private Connection connection;//here we store commection
private String userName = null;//user nickname
private final Pattern authCmdPattern = Pattern.compile("^\\/auth ([\\S]+).*");//income auth command pattern
/**
* Get users request command pattern
*/
private final Pattern getUsersCmdPattern = Pattern.compile("^\\/getUsers.*");
/**
* help request command pattern
*/
private final Pattern helpCmdPattern = Pattern.compile("^\\/help.*");
/**
* Executed on new connection opening
*
* @param connection
*/
@Override
public void onOpen(Connection connection) {
logger.debugT("ChatWebSocketHandler onOpen ");
// Saving connection into ChatWebSocket::connection
this.connection = connection;
// Adding this handler to global set
// ChatWebSocketHandler::webSockets
webSockets.add(this);
}
/**
* executing on ne message from client
*
* @param data
*/
@Override
public void onMessage(String data) {
logger.debugT("ChatWebSocketHandler onMessage ");
// If we have authorization command
if (authCmdPattern.matcher(data).matches()) {
Matcher matcher = authCmdPattern.matcher(data);
matcher.find();
// Setting new user nickname
userName = matcher.group(1);
try {
// Iterating over sockets
// ChatWebSocketHandler::webSockets
for (ChatWebSocket webSocket : webSockets) {
// sending message about new user connected
webSocket.connection.sendMessage("inf|"
+ (webSocket.equals(this) ? "You have entered" : ("User entered chat <b>" + userName + "</b>")));
}
} catch (IOException x) {
// All errors will lead to disconnect
connection.disconnect();
}
// If we have get users command
} else if (getUsersCmdPattern.matcher(data).matches()) {
String userList = "";
// Iterating over sockets
for (ChatWebSocket webSocket : webSockets) {
userList += webSocket.userName + ", ";
}
userList = userList.substring(0, userList.length() - 2);
try {
//Sending active users list
connection.sendMessage("inf|User list: " + userList);
} catch (IOException x) {
// All errors will lead to disconnect
connection.disconnect();
}
// If we have /help command
} else if (helpCmdPattern.matcher(data).matches()) {
String helpMessage = "You can send messages by just putting them " + into the field and pressing Enter.<br />"
+ "Chat can handle commands:<br />" + "<ul><li><b>/help</b> - to print this message</li>"
+ "<li><b>/getUsers</b> - to get active users list</li>" + "<li><b>/auth <i>Nickname</i></b> - for authorization</li></ul>";
try {
// Sending instruction
connection.sendMessage("inf|" + helpMessage);
} catch (IOException x) {
// All errors will lead to disconnect
connection.disconnect();
}
// If we have message(not a command)
} else {
try {
// If current user did't authorize
if (userName == null) {
connection.sendMessage("err|U did't log on<br />" + "Use command <b>/help</b> for help");
return;
}
// Iterating over sockets
// ChatWebSocketHandler::webSockets
for (ChatWebSocket webSocket : webSockets) {
// sending in message for everyone exept author
// for author - out flag
webSocket.connection.sendMessage((webSocket.equals(this) ? "out|" : ("in|" + userName + "|")) + data);
}
} catch (IOException x) {
// All errors will lead to disconnect
connection.disconnect();
}
}
}
/**
* Executing when user disconnects from server
*
* @param closeCode
* @param message
*/
@Override
public void onClose(int closeCode, String message) {
logger.debugT("ChatWebSocketHandler onClose closeCode: "+closeCode+" message: "+message);
// Removing ourselves from global set
// ChatWebSocketHandler::webSockets
webSockets.remove(this);
}
}
}
3. Write WebSocket Server.
I used simple HttpServlet to start server (placed startServer method in its init method), but you can start server from any kind of application, e.g. WD4J or just a jar with main method etc.
private void startServer() {
logger.debugT("Starting Jetty");
try {
// Creating Jetty Server on 8081 port
this.server = new Server(8081);
logger.debugT("Creating Jetty Server on 8081 port");
// Registerring ChatWebSocketHandler in Jetty server
ChatWebSocketHandler chatWebSocketHandler = new ChatWebSocketHandler();
logger.debugT("Registerring ChatWebSocketHandler in Jetty server");
chatWebSocketHandler.setHandler(new DefaultHandler());
server.setHandler(chatWebSocketHandler);
// Starting jetty
server.start();
logger.debugT("Jetty started");
} catch (Throwable e) {
logger.errorT("startServer error!");
e.printStackTrace();
}
}
4. Write some client code.
You can write a simple html with javascript code:
var websocket;
window.onload = function() {
//web sockets starts here
websocket = new WebSocket('ws://host:8081');
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
Or you can use for test https://www.websocket.org/echo.html
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
10 | |
7 | |
5 | |
5 | |
5 | |
4 | |
4 | |
4 | |
3 | |
3 |