Skip to content

declarative websockets#3917

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

declarative websockets#3917
kliushnichenko wants to merge 2 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

super(context, clazz);
}

public static WsRouter parse(MvcContext context, TypeElement controller) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is your parse/selection method. Here you need to filter by Path + [WebSocket] annotation. Is Path required??

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