Apache Tomcat Simple Comet Servlet - java

I'm trying create very simple Comet Servlet which will push Hello World message to subscribers:
#WebServlet("/ChatServlet")
public class ChatServlet extends HttpServlet implements CometProcessor {
private static final long serialVersionUID = 1L;
private MessageSender messageSender = null;
private static final Integer TIMEOUT = 60 * 1000;
public void init(ServletConfig config) throws ServletException {
messageSender = new MessageSender();
Thread messageSenderThread =
new Thread(messageSender);
messageSenderThread.setDaemon(true);
messageSenderThread.start();
}
public void destroy() {
// messageSender.stop();
messageSender = null;
}
#Override
public void event(CometEvent event) throws IOException, ServletException {
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();
if (event.getEventType() == CometEvent.EventType.BEGIN) {
request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);
System.out.println("Begin for session: " + request.getSession(true).getId());
messageSender.setConnection(response);
}
else if (event.getEventType() == CometEvent.EventType.ERROR) {
System.out.println("Error for session: " + request.getSession(true).getId());
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
System.out.println("End for session: " + request.getSession(true).getId());
event.close();
} else if (event.getEventType() == CometEvent.EventType.READ) {
throw new UnsupportedOperationException("This servlet does not accept data");
}
}
}
and then my Runnable looks like this:
public class MessageSender implements Runnable {
protected boolean running = true;
protected final List<String> messages = new ArrayList<String>();
private ServletResponse connection;
public synchronized void setConnection(ServletResponse connection){
this.connection = connection;
notify();
}
#Override
public void run() {
while (running) {
if (messages.size() == 0) {
try {
synchronized (messages) {
messages.wait();
}
} catch (InterruptedException e) {
// Ignore
}
}
String[] pendingMessages = null;
synchronized (messages) {
pendingMessages = messages.toArray(new String[0]);
messages.clear();
}
try {
if (connection == null){
try{
synchronized(this){
wait();
}
} catch (InterruptedException e){
// Ignore
}
}
PrintWriter writer = connection.getWriter();
writer.println("hello World");
System.out.println("Writing Hello World");
writer.flush();
writer.close();
connection = null;
System.out.println("Closing connection");
} catch (IOException e) {
System.out.println("IOExeption sending message"+e.getMessage());
}
}
}
}
now my Dojo cometd code looks like this:
<script src="dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojox.cometd");
dojo.addOnLoad(function(){
dojox.cometd.init("ChatServlet");
dojox.cometd.subscribe("ChatServlet", window, "alertMessage");
});
function alertMessage(message) {
alert("Message: " + message);
}
</script>
Now when I load client I'm getting the following error:
Begin for session: C898A372F1B1199C04CA308F715ABC36Nov 6, 2011 2:00:48 PM org.apache.catalina.core.StandardWrapperValve event
SEVERE: Servlet.service() for servlet [com.vanilla.servlet.ChatServlet] in context with path [/Servlet3Comet] threw exception
java.lang.UnsupportedOperationException: This servlet does not accept data
at com.vanilla.servlet.ChatServlet.event(ChatServlet.java:75)
Error for session: C898A372F1B1199C04CA308F715ABC36
End for session: C898A372F1B1199C04CA308F715ABC36
What am I doing wrong?
Why does cometD subscription invokes CometEvent.EventType.READ?
Does anybody have any working comet example?
P.S: I did switch to Nio according to Tomcat configuration.

Documentation for init(ServletConfig):
public void init(ServletConfig config) throws ServletException Called
by the servlet container to indicate to a servlet that the servlet is
being placed into service.
See Servlet#init. This implementation stores the ServletConfig object
it receives from the servlet container for later use. When overriding
this form of the method, call super.init(config).
And Documentation for init():
public void init() throws ServletException A convenience method which
can be overridden so that there's no need to call super.init(config).
Instead of overriding init(ServletConfig), simply override this method
and it will be called by GenericServlet.init(ServletConfig config).
The ServletConfig object can still be retrieved via
getServletConfig().
When overriding init(ServletConfig), your first call must be super.init(config);

Related

Java WebSocketStompClient connect not returning

I'm trying to set up a simple application using Spring and websockets and have problem setting up the connection.
I have looked around for examples but almost all searches lead to the chat sample which is not what I am trying to do.
My app is a task/monitoring scenario. I want to be able to send a request to the server and then monitor the progress
of the task on the same connection.
I have seen the chat sample, portfolio sample and various other comments on SO but I'm not sure what I'm missing.
I'm new to Spring and websockets.
The logging appears to show a successful connection for the /info path but then the call to stompClient.connect() fails to return.
14:02:26.330 [main] DEBUG org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport - Executing SockJS Info request, url=http://localhost:9080/Vault713MQServer/websocket/info
14:02:26.480 [main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:9080/Vault713MQServer/websocket/info"
14:02:26.559 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:9080/Vault713MQServer/websocket/info" resulted in 200 (OK)
14:02:26.578 [main] DEBUG org.springframework.web.socket.sockjs.client.WebSocketTransport - Starting WebSocket session url=ws://localhost:9080/Vault713MQServer/websocket/369/ee89fc87489842af868c0f0452aacf13/websocket
14:02:26.578 [main] DEBUG org.springframework.web.socket.client.standard.StandardWebSocketClient - Connecting to ws://localhost:9080/Vault713MQServer/websocket/369/ee89fc87489842af868c0f0452aacf13/websocket
14:02:26.693 [WebSocketClient-AsyncIO-1] DEBUG org.springframework.web.socket.sockjs.client.WebSocketClientSockJsSession - Processing SockJS open frame in WebSocketClientSockJsSession[id='ee89fc87489842af868c0f0452aacf13, url=ws://localhost:9080/Vault713MQServer/websocket]
14:02:26.693 [WebSocketClient-AsyncIO-1] DEBUG org.springframework.messaging.simp.stomp.DefaultStompSession - Connection established in session id=07e2d0cc-6f99-95d5-7014-614aad3e0f13
If I connect to 'http://localhost:9080/Vault713MQServer/websocket/info' in a browser it returns:
{"entropy":1894449220,"origins":["*:*"],"cookie_needed":true,"websocket":true}
On the server side I have:
/* WebSocketConfig.java */
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer
{
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry)
{
registry.addHandler(myHandler(), "/websocket").withSockJS();
}
#Bean
public ServerHandler myHandler()
{
return new ServerHandler();
}
}
/* ServerHandler.java */
public class ServerHandler extends TextWebSocketHandler
{
private final Logger logger = Logger.getLogger(this.getClass().getName());
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// TODO Auto-generated method stub
logger.log(Level.INFO, "Connection clodes with websocket server: session id {0}", session.getId());
super.afterConnectionClosed(session, status);
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// TODO Auto-generated method stub
logger.log(Level.INFO, "Connected user with websocket server: session id {0}", session.getId());
super.afterConnectionEstablished(session);
}
#Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
// TODO Auto-generated method stub
super.handleMessage(session, message);
}
#Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
// TODO Auto-generated method stub
super.handleTransportError(session, exception);
}
}
On the client side I have:
/* Clientside - Vault713MQClient.java */
public class Vault713MQClient
{
static public class MyStompSessionHandler
extends StompSessionHandlerAdapter
{
private String userId;
public MyStompSessionHandler(String userId)
{
this.userId = userId;
}
private void showHeaders(StompHeaders headers)
{
for (Map.Entry<String, List<String>> e : headers.entrySet())
{
System.err.print(" " + e.getKey() + ": ");
boolean first = true;
for (String v : e.getValue())
{
if (!first)
{
System.err.print(", ");
}
System.err.print(v);
first = false;
}
System.err.println();
}
}
private void sendJsonMessage(StompSession session)
{
session.send("/websocket", "hello from spring");
}
private void subscribeTopic(String topic, StompSession session)
{
session.subscribe(topic, new StompFrameHandler()
{
#Override
public Type getPayloadType(StompHeaders headers)
{
return String.class;
}
#Override
public void handleFrame(StompHeaders headers,
Object payload)
{
System.err.println(payload.toString());
}
});
}
#Override
public void afterConnected(StompSession session,
StompHeaders connectedHeaders)
{
System.err.println("Connected! Headers:");
showHeaders(connectedHeaders);
// subscribeTopic("/topic/messages", session);
// sendJsonMessage(session);
}
}
public static void main(String args[]) throws Exception
{
WebSocketClient simpleWebSocketClient = new StandardWebSocketClient();
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport(simpleWebSocketClient));
SockJsClient sockJsClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
String url = "ws://localhost:9080/Vault713MQServer/websocket";
String userId = "spring-" + ThreadLocalRandom.current().nextInt(1, 99);
StompSessionHandler sessionHandler = new MyStompSessionHandler(userId);
StompSession session = stompClient.connect(url, sessionHandler).get();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for (;;)
{
System.out.print(userId + " >> ");
System.out.flush();
String line = in.readLine();
if (line == null)
{
break;
}
if (line.length() == 0)
{
continue;
}
session.send("/websocket", line);
// ClientMessage msg = new ClientMessage(userId, line);
// session.send("/app/chat/java", msg);
}
}
}
Can anyone see what I have done wrong or does anyone have a simple complete example of what I am trying to do?
Many thanks.
KCM

Data inconsistency in multithreaded environment

I have created an application which reads & writes into a remote file. I have different files (A.properties, B.properties, C.properties) in different directories (folder-1, folder-2, folder-3). Each directory has the same filename with different data.
I have implemented concurrency in my application by using the LockRegistry provided by this other answer. The issue is that if a thread is accessing A.properties while another thread accesses B.properties, the propertyMap displayed to the end user will contain both data from property files. How can I resolve this issue?
My code:
public class UDEManager
{
private Map<String, String> propertyMap = new TreeMap<>();
HttpSession session = null;
public UDEPropertyManager()
{
super();
}
public void init(ServletConfig config) throws ServletException
{
super.init(config);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// Code for calling thread for read/write operations into remote
// file and fill the propertyMap
}
}
class WebAppProperty implements Runnable
{
private WebApp webapp; // folder-1
private String propertyFile; // A.properties
private String keyValue; //messages-title=Messages
private LockType mode;
public String getPropertyFile()
{
return propertyFile;
}
public void setPropertyFile(String propertyFile)
{
this.propertyFile = propertyFile;
}
#Override
public void run()
{
try {
LockRegistry.INSTANCE.acquire(propertyFile, mode);
if (this.mode == LockType.WRITE) {
writeToPropertyFile();
} else if (this.mode == LockType.READ) {
getProperty(this.webapp, this.propertyFile);
}
} catch (Exception ie) {
sysoutAndLog("Thread is Interrupted");
ie.printStackTrace();
} finally {
LockRegistry.INSTANCE.release(propertyFile, mode);
}
}
private boolean getProperty(WebApp webapp, String property)
{
try {
// read from file and put it into Map instance variable
// of calling class (UDEManager)
propertyMap.put(key, value);
} catch(Exception e) {
sysoutAndLog("Error while reading property ");
e.printStackTrace();
}
return false;
}
private void writeToPropertyFile()
{
try {
// Write data into remote file
} catch (Exception e) {
sysoutAndLog("exception while writing to file.");
e.printStackTrace();
}
}
}
You should associate the properties map with the user session or request

how to automatically init an object after jetty setup?

My java application starts a jetty server container.
Is there any "after init" method that jetty can call after it's up
that will construct a specific BL object and call one of its methods?
This is my code to init jetty:
public static void main(String[] args) throws Exception {
SdkServiceConfig.s.initLog();
logger.error("testing log");
final int port = 8083;
...
webappContext.setExtractWAR(false);
handlers.addHandler(webappContext);
// Adding the handlers to the server.
jettyServer.setHandler(handlers);
try {
jettyServer.start();
jettyServer.join();
} catch (Exception ex) {
logger.error("failed to init jetty server", ex);
System.out.println(ex);
throw new RuntimeException(ex);
} finally {
jettyServer.destroy();
}
}
and then i call specific url to trigger my server state initialization.
Server.java
#Path("/waitRequests")
#GET
#Consumes(MediaType.APPLICATION_JSON + ";charset=utf-8")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public void waitRequests() throws Exception {
System.out.println("waitRequests");
PubSubFactory pubSubFactory = new PubSubFactory();
pubSubFactory.init(SdkServiceConfig.s.GCP_PROJECT_ID, new File(SdkServiceConfig.s.SDK_PUBSUB_CLIENT_SECRET));
I wish it would be something like:
#Path("/sdk-service")
public class SdkOperation {
private final org.apache.log4j.Logger logger = LoggingUtils.getLogger();
private final CofmanService cofmanService;
// private SdkRequestRepo sdkRequestRepo;
private CustomPublisher resultPublisher;
public SdkOperation() throws Exception {
this(new CofmanServiceNet(), null);
}
Server server = new Server();
server.waitRequests()

How to test the connection in QuickFix/J

I wrote an QuickFix program and created a config file.. Now, when I'm running my application, nothing happens.. So I ask me, how I can recognize if the connection (my program is of type initiator) is established or not.
I've added to the methods that I implement from the Application interface
void fromAdmin(Message message, SessionID sessionId)
void fromApp(Message message, SessionID sessionId)
void onCreate(SessionID sessionId)
void onLogon(SessionID sessionId)
void onLogout(SessionID sessionId)
void toAdmin(Message message, SessionID sessionId)
void toApp(Message message, SessionID sessionId)
a System.out and a logger.info, but nothing of them fires something.
If a connection established, the methods onCreate and onLogon are invoked, or?! So the System.outs in these methods should write something..
But are there any other opportunitys to check wheter the connection established, respectively the config file is valid.
P.S.: I use the SessionSettings to read the config File in.. But I can't find a method in the SessionSettings like validateConfigFile() or something like that.
Has your config file got FileLogPath=log?
Then to debug you look at the FIX messages log, usually in the bin/debug/log directory, which is where it would be for the above config file. If it's not a security risk then paste your config file here please. Also, yes add the System.Out to your Application interface. Here's mine:
public void FromApp(Message msg, SessionID s)
{
Console.WriteLine("IN: " + msg.ToString());
You can download Banzai application which is an accpetor/iniciator with grafic interface. You can iniciate a session, and send/receive messages from your app.
Main class you need in your Iniciator
public class Banzai {
private static final CountDownLatch shutdownLatch = new CountDownLatch(1);
/** enable logging for this class */
private static Logger log = LoggerFactory.getLogger(Banzai.class);
private static Banzai banzai;
private boolean initiatorStarted = false;
private Initiator initiator = null;
private JFrame frame = null;
public Banzai(String[] args) throws Exception {
InputStream inputStream = null;
if (args.length == 0) {
inputStream = new BufferedInputStream(new FileInputStream(new File("config/banzai.cfg")));
} else if (args.length == 1) {
inputStream = new FileInputStream(args[0]);
}
if (inputStream == null) {
System.out.println("usage: " + Banzai.class.getName() + " [configFile].");
return;
}
SessionSettings settings = new SessionSettings(inputStream);
inputStream.close();
boolean logHeartbeats = Boolean.valueOf(System.getProperty("logHeartbeats", "true")).booleanValue();
OrderTableModel orderTableModel = new OrderTableModel();
ExecutionTableModel executionTableModel = new ExecutionTableModel();
BanzaiApplication application = new BanzaiApplication(orderTableModel, executionTableModel);
MessageStoreFactory messageStoreFactory = new FileStoreFactory(settings);
LogFactory logFactory = new ScreenLogFactory(true, true, true, logHeartbeats);
MessageFactory messageFactory = new DefaultMessageFactory();
initiator = new SocketInitiator(application, messageStoreFactory, settings, logFactory, messageFactory);
frame = new BanzaiFrame(orderTableModel, executionTableModel, application);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public synchronized void logon() {
if (!initiatorStarted) {
try {
initiator.start();
initiatorStarted = true;
} catch (Exception e) {
log.error("Logon failed", e);
}
} else {
Iterator<SessionID> sessionIds = initiator.getSessions().iterator();
while (sessionIds.hasNext()) {
SessionID sessionId = (SessionID) sessionIds.next();
Session.lookupSession(sessionId).logon();
}
}
}
public void logout() {
Iterator<SessionID> sessionIds = initiator.getSessions().iterator();
while (sessionIds.hasNext()) {
SessionID sessionId = (SessionID) sessionIds.next();
Session.lookupSession(sessionId).logout("user requested");
}
}
public void stop() {
shutdownLatch.countDown();
}
public JFrame getFrame() {
return frame;
}
public static Banzai get() {
return banzai;
}
public static void main(String args[]) throws Exception {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
log.info(e.getMessage(), e);
}
banzai = new Banzai(args);
if (!System.getProperties().containsKey("openfix")) {
banzai.logon();
}
shutdownLatch.await();
}
}
NOTE: The information in your .settings has to match with the information you have in your code.
After you start both codes you will start to receive logon, and heartbeat messages.

Full duplex HTTP Upgrade using non-blocking Servlet 3.1 API

I asked a question regarding HTTP Upgrade handling in Servlet 3.1 on wildfly-dev mailing list how to develop simple bi-directional full-duplex echo protocol. After few days of discussion we concluded that the following example is somewhat correct:
#WebServlet(urlPatterns = "/upgrade")
public class AsyncEchoUpgradeServlet extends HttpServlet {
private static final long serialVersionUID = -6955518532146927509L;
#Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse resp) throws ServletException, IOException {
req.upgrade(Handler.class);
}
public static class Handler implements HttpUpgradeHandler {
#Override
public void init(final WebConnection wc) {
Listener listener = new Listener(wc);
try {
// we have to set the write listener before the read listener
// otherwise the output stream could be written to before it is
// in async mode
wc.getOutputStream().setWriteListener(listener);
wc.getInputStream().setReadListener(listener);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
#Override
public void destroy() {
}
}
private static class Listener implements WriteListener, ReadListener {
private final WebConnection connection;
private final Queue<String> queue = new ArrayDeque<String>();
private Listener(final WebConnection connection) {
this.connection = connection;
}
#Override
publicvoid onDataAvailable() throws IOException {
byte[] data = new byte[100];
while (connection.getInputStream().isReady()) {
int read;
if ((read = connection.getInputStream().read(data)) != -1) {
queue.add(new String(data, 0, read));
}
onWritePossible();
}
}
#Override
public void onAllDataRead() throws IOException {
}
#Override
public void onWritePossible() throws IOException {
while (!queue.isEmpty() && connection.getOutputStream().isReady()) {
String data = queue.poll();
connection.getOutputStream().write(data.getBytes());
}
}
#Override
public void onError(final Throwable t) {
}
}
}
It actually works and it is the only example exchanged during our discussion that works well and we don't have to create any additional threads to handle both input and output.
What bothers me in this example is that we call explicitly onWritePossible() from within onDataAvailable().
Does anyone have any other example on HTTP Upgrade using Servlet 3.1 API? Is the example shown above correct?

Categories