Repository: jooby-project/jooby
Issue: Websockets onClose not working

Hi!

For some weird reason, the onClose event is not called on my WebSockets when setting a handler using the configurator.

Regards
Dominik

Changed files:
modules/jooby-utow/src/main/java/io/jooby/internal/utow/UtowWebSocket.java, jooby/src/main/java/io/jooby/Server.java, modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWebSocket.java

diff --git a/jooby/src/main/java/io/jooby/Server.java b/jooby/src/main/java/io/jooby/Server.java
index e0197145c..8868970aa 100644
--- a/jooby/src/main/java/io/jooby/Server.java
+++ b/jooby/src/main/java/io/jooby/Server.java
@@ -139,8 +139,10 @@ public interface Server {
       String message = cause.getMessage();
       if (message != null) {
         String msg = message.toLowerCase();
-        return msg.contains("reset by peer") || msg.contains("broken pipe") || msg
-            .contains("forcibly closed");
+        return msg.contains("reset by peer")
+            || msg.contains("broken pipe")
+            || msg.contains("forcibly closed")
+            || msg.contains("connection reset");
       }
     }
     return (cause instanceof ClosedChannelException) || (cause instanceof EOFException);
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWebSocket.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWebSocket.java
index fe1d2dfda..434461504 100644
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWebSocket.java
+++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWebSocket.java
@@ -5,6 +5,17 @@
  */
 package io.jooby.internal.netty;
 
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.annotation.Nonnull;
+
 import io.jooby.Context;
 import io.jooby.Router;
 import io.jooby.Server;
@@ -24,16 +35,6 @@ import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
 import io.netty.handler.codec.http.websocketx.WebSocketFrame;
 import io.netty.util.AttributeKey;
 
-import javax.annotation.Nonnull;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-
 public class NettyWebSocket implements WebSocketConfigurer, WebSocket, ChannelFutureListener {
   /** All connected websocket. */
   private static final ConcurrentMap<String, List<NettyWebSocket>> all = new ConcurrentHashMap<>();
@@ -155,20 +156,22 @@ public class NettyWebSocket implements WebSocketConfigurer, WebSocket, ChannelFu
 
   private void handleMessage(WebSocketFrame frame) {
     try {
-      if (frame.isFinalFragment()) {
-        ByteBuf content;
-        if (buffer != null) {
-          buffer.writeBytes(frame.content());
-          content = buffer;
-          buffer = null;
+      if (messageCallback != null) {
+        if (frame.isFinalFragment()) {
+          ByteBuf content;
+          if (buffer != null) {
+            buffer.writeBytes(frame.content());
+            content = buffer;
+            buffer = null;
+          } else {
+            content = frame.content();
+          }
+          WebSocketMessage message = WebSocketMessage.create(getContext(), array(content));
+
+          fireCallback(webSocketTask(() -> messageCallback.onMessage(this, message), false));
         } else {
-          content = frame.content();
+          buffer = Unpooled.copiedBuffer(frame.content());
         }
-        WebSocketMessage message = WebSocketMessage.create(getContext(), array(content));
-
-        fireCallback(webSocketTask(() -> messageCallback.onMessage(this, message), false));
-      } else {
-        buffer = Unpooled.copiedBuffer(frame.content());
       }
     } finally {
       frame.release();
diff --git a/modules/jooby-utow/src/main/java/io/jooby/internal/utow/UtowWebSocket.java b/modules/jooby-utow/src/main/java/io/jooby/internal/utow/UtowWebSocket.java
index db5f5c60d..85d3aec73 100644
--- a/modules/jooby-utow/src/main/java/io/jooby/internal/utow/UtowWebSocket.java
+++ b/modules/jooby-utow/src/main/java/io/jooby/internal/utow/UtowWebSocket.java
@@ -198,12 +198,19 @@ public class UtowWebSocket extends AbstractReceiveListener
   @Override protected void onError(WebSocketChannel channel, Throwable x) {
     // should close?
     if (Server.connectionLost(x) || SneakyThrows.isFatal(x)) {
-      handleClose(WebSocketCloseStatus.SERVER_ERROR);
+      if (channel.isOpen()) {
+        handleClose(WebSocketCloseStatus.SERVER_ERROR);
+      }
     }
 
     if (onErrorCallback == null) {
-      ctx.getRouter().getLog()
-          .error("Websocket resulted in exception: {}", ctx.getRequestPath(), x);
+      if (Server.connectionLost(x)) {
+        ctx.getRouter().getLog()
+            .debug("Websocket connection lost: {}", ctx.getRequestPath(), x);
+      } else {
+        ctx.getRouter().getLog()
+            .error("Websocket resulted in exception: {}", ctx.getRequestPath(), x);
+      }
     } else {
       onErrorCallback.onError(this, x);
     }
@@ -215,8 +222,10 @@ public class UtowWebSocket extends AbstractReceiveListener
 
   @Override protected void onCloseMessage(CloseMessage cm,
       WebSocketChannel channel) {
-    handleClose(WebSocketCloseStatus.valueOf(cm.getCode())
-        .orElseGet(() -> new WebSocketCloseStatus(cm.getCode(), cm.getReason())));
+    if (channel.isOpen()) {
+      handleClose(WebSocketCloseStatus.valueOf(cm.getCode())
+          .orElseGet(() -> new WebSocketCloseStatus(cm.getCode(), cm.getReason())));
+    }
   }
 
   private void handleClose(WebSocketCloseStatus closeStatus) {