Skip to content

declarative websockets#3917

Open
kliushnichenko wants to merge 3 commits intojooby-project:mainfrom
kliushnichenko:feat/declarative-websockets
Open

declarative websockets#3917
kliushnichenko wants to merge 3 commits intojooby-project:mainfrom
kliushnichenko:feat/declarative-websockets

Conversation

@kliushnichenko
Copy link
Copy Markdown
Contributor

So,

@WebSocketRoute("/chat/{username}")
public class ChatWebsocket {

  @OnConnect
  public String onConnect(WebSocket ws, Context ctx) {
    return "welcome";
  }

  @OnMessage
  public Map<String, String> onMessage(WebSocket ws, Context ctx, WebSocketMessage message) {
    return Map.of("echo", message.value());
  }

  @OnClose
  public void onClose(WebSocket ws, Context ctx, WebSocketCloseStatus status) {}

  @OnError
  public void onError(WebSocket ws, Context ctx, Throwable cause) {}
}

Will produce

@io.jooby.annotation.Generated(ChatWebsocket.class)
public class ChatWebsocketWs_ implements io.jooby.Extension {
    protected java.util.function.Function<io.jooby.Context, ChatWebsocket> factory;

    public ChatWebsocketWs_() {
      this(io.jooby.SneakyThrows.singleton(ChatWebsocket::new));
    }

    public ChatWebsocketWs_(ChatWebsocket instance) {
      setup(ctx -> instance);
    }

    public ChatWebsocketWs_(io.jooby.SneakyThrows.Supplier<ChatWebsocket> provider) {
      setup(ctx -> provider.get());
    }

    public ChatWebsocketWs_(io.jooby.SneakyThrows.Function<Class<ChatWebsocket>, ChatWebsocket> provider) {
      setup(ctx -> provider.apply(ChatWebsocket.class));
    }

    private void setup(java.util.function.Function<io.jooby.Context, ChatWebsocket> factory) {
      this.factory = factory;
    }

    public void install(io.jooby.Jooby app) throws Exception {
      app.ws("/chat/{username}", this::wsInit);
    }

    private void wsInit(io.jooby.Context ctx, io.jooby.WebSocketConfigurer configurer) {
      /** See {@link ChatWebsocket#onConnect(io.jooby.WebSocket, io.jooby.Context)} */
      configurer.onConnect(ws -> {
        var c = this.factory.apply(ctx);
        var __wsReturn = c.onConnect(ws, ctx);
        ws.send(__wsReturn);
      });

      /** See {@link ChatWebsocket#onMessage(io.jooby.WebSocket, io.jooby.Context, io.jooby.WebSocketMessage)} */
      configurer.onMessage((ws, message) -> {
        var c = this.factory.apply(ctx);
        var __wsReturn = c.onMessage(ws, ctx, message);
        ws.render(__wsReturn);
      });

      /** See {@link ChatWebsocket#onClose(io.jooby.WebSocket, io.jooby.Context, io.jooby.WebSocketCloseStatus)} */
      configurer.onClose((ws, status) -> {
        var c = this.factory.apply(ctx);
        c.onClose(ws, ctx, status);
      });

      /** See {@link ChatWebsocket#onError(io.jooby.WebSocket, io.jooby.Context, Throwable)} */
      configurer.onError((ws, cause) -> {
        var c = this.factory.apply(ctx);
        c.onError(ws, ctx, cause);
      });
    }
}

and can be registered over

{
    ws(new ChatWebsocketWs_());
}

Annotation name is @WebSocketRoute to avoid the clash with io.jooby.WebSocket interface.
If we want a nice annotation like @WebSocket -> need to rename io.jooby.WebSocket into io.jooby.WebSocketConnection or something.

@jknack
Copy link
Copy Markdown
Member

jknack commented Apr 20, 2026

Will, look closer in a bit. For now why not @Path instead of @WebSocketRoute. Also, should we do the same for SSE?

@kliushnichenko
Copy link
Copy Markdown
Contributor Author

why not @path instead of @WebSocketRoute

@WebSocketRoute makes it vividly recognizable as a WS handler, easier to catch in apt.

@Path can be. If @Path + @OnConnect/@OnMessage is sufficient to catch in apt

@jknack
Copy link
Copy Markdown
Member

jknack commented Apr 20, 2026

Think path fit better and we don't introduce a new annotation.

@jknack
Copy link
Copy Markdown
Member

jknack commented Apr 20, 2026

@kliushnichenko
Copy link
Copy Markdown
Contributor Author

upd:

  • removed @WebSocketRoute in favor of @Path
  • @Path is optional
  • @OnConnect or @OnMessage annotation is the minimal required condition to treat the class as a WS handler

@jknack
Copy link
Copy Markdown
Member

jknack commented Apr 21, 2026

looks good. Should we add support for parameter binding? Beside the WebSocketMessage, like onMessage(MyBean bean); onMessage(String message);, etc... WebSocketMessage it is a io.jooby.value.Value we probably can reuse binding from Rest/MVC generator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants