{"id":34331064,"url":"https://github.com/zyro23/grails-spring-websocket","last_synced_at":"2025-12-17T18:04:46.160Z","repository":{"id":11929437,"uuid":"14497929","full_name":"zyro23/grails-spring-websocket","owner":"zyro23","description":null,"archived":false,"fork":false,"pushed_at":"2025-10-22T06:07:43.000Z","size":695,"stargazers_count":93,"open_issues_count":1,"forks_count":29,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-10-22T07:26:17.142Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zyro23.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2013-11-18T16:23:08.000Z","updated_at":"2025-10-22T06:07:48.000Z","dependencies_parsed_at":"2024-11-04T13:29:45.421Z","dependency_job_id":"6aecc80a-f0cb-42af-9372-bc517c38ea0d","html_url":"https://github.com/zyro23/grails-spring-websocket","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/zyro23/grails-spring-websocket","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zyro23%2Fgrails-spring-websocket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zyro23%2Fgrails-spring-websocket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zyro23%2Fgrails-spring-websocket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zyro23%2Fgrails-spring-websocket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zyro23","download_url":"https://codeload.github.com/zyro23/grails-spring-websocket/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zyro23%2Fgrails-spring-websocket/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27785322,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-17T02:00:08.291Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-17T18:03:25.168Z","updated_at":"2025-12-17T18:04:46.149Z","avatar_url":"https://github.com/zyro23.png","language":"Groovy","funding_links":[],"categories":["网络编程"],"sub_categories":["Spring Cloud框架"],"readme":"# Spring Websocket Grails Plugin\n\nThis plugin aims at making the websocket support introduced in Spring 4.0 available to Grails applications.\n\nYou can also use the corresponding Spring docs/apis/samples as a reference.\n\nThat is mentioned multiple times in this readme because there is everything explained in fine detail.\n\nVersion compatibility:\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003cth\u003ePlugin version\u003c/th\u003e\n        \u003cth\u003eGrails version\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eio.github.zyro23:grails-spring-websocket:2.7.x\u003c/td\u003e\n        \u003ctd\u003e7.0.0+\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eio.github.zyro23:grails-spring-websocket:2.6.x\u003c/td\u003e\n        \u003ctd\u003e6.0.0+\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eorg.grails.plugins:grails-spring-websocket:2.5.x\u003c/td\u003e\n        \u003ctd\u003e4.0.0+\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eorg.grails.plugins:grails-spring-websocket:2.4.x\u003c/td\u003e\n        \u003ctd\u003e3.2.7+\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n## Installation\n\nTo install the plugin into a Grails application add the following line to your `build.gradle` dependencies section:\n\n    implementation \"io.github.zyro23:grails-spring-websocket:2.7.0\"\n\nPlugin releases are published to maven central.\n\n### Snapshots\n\nTo install a `-SNAPSHOT` version, add the snapshot repository:\n\n    repositories {\n        maven {\n            url = \"https://central.sonatype.com/repository/maven-snapshots\"\n        }\n    }\n\nAnd add the following line to your `build.gradle` dependencies section:\n\n    implementation \"io.github.zyro23:grails-spring-websocket:2.8.0-SNAPSHOT\"\n\nPlugin snapshots are published to the maven central snapshot repository which has an automatic cleanup policy (90 days).\n\n## Usage\n\nThe plugin makes the Spring websocket/messaging web-mvc annotations usable in Grails, too.\n\nThose annotations can be used in:\n* Regular Grails controllers\n* `WebSocket` Grails artefacts (`./grailsw create-web-socket my.package.name.MyWebSocket`)\n* Spring `@Controller` beans\n\nI think basic usage is explained best by example code.\n\nBut: the code below is just some very minimal it-works proof.\n\nCheck the Spring docs/apis/samples for more advanced use-cases, e.g. security and authentication.\n\n### Controller (annotated handler method)\n\n*/grails-app/controllers/example/ExampleController.groovy*:\n\n```groovy\npackage example\n\nimport org.springframework.messaging.handler.annotation.MessageMapping\nimport org.springframework.messaging.handler.annotation.SendTo\n\nclass ExampleController {\n\n    def index() {}\n\n    @MessageMapping(\"/hello\")\n    @SendTo(\"/topic/hello\")\n    protected String hello(String world) {\n        return \"hello, ${world}!\"\n    }\n\n}\n```\n\nUnless you want your handler method to be exposed as a Grails controller action, you should define the annotated method as protected or add an additional annotation `@grails.web.controllers.ControllerMethod`.\n\nAlternatively, `WebSocket` Grails artefacts and/or Spring `@Controller` beans can be used as well, for example:\n\n*/grails-app/websockets/example/ExampleWebSocket.groovy*:\n\n```groovy\npackage example\n\nimport org.springframework.messaging.handler.annotation.MessageMapping\nimport org.springframework.messaging.handler.annotation.SendTo\n\nclass ExampleWebSocket {\n\n    @MessageMapping(\"/hello\")\n    @SendTo(\"/topic/hello\")\n    String hello(String world) {\n        return \"hello, ${world}!\"\n    }\n\n}\n```\n\n### Client-side (stomp.js)\n\n*/grails-app/views/example/index.gsp*:\n\n```gsp\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003cmeta name=\"layout\" content=\"main\"/\u003e\n\n        \u003casset:javascript src=\"application\" /\u003e\n        \u003casset:javascript src=\"spring-websocket\" /\u003e\n\n        \u003cscript type=\"text/javascript\"\u003e\n            $(() =\u003e {\n                var client = new StompJs.Client({\n                    brokerURL: \"ws${createLink(uri: '/stomp', absolute: true).replaceFirst('(?i)http', '')}\",\n                    onConnect: () =\u003e {\n                        client.subscribe(\"/topic/hello\", (message) =\u003e {\n                            $(\"#helloDiv\").append(message.body);\n                        });\n                    },\n                });\n                client.activate();\n                $(\"#helloButton\").click(() =\u003e {\n                    client.publish({ destination: \"/app/hello\", body: \"world\" });\n                });\n            });\n        \u003c/script\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cbutton id=\"helloButton\"\u003ehello\u003c/button\u003e\n        \u003cdiv id=\"helloDiv\"\u003e\u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThis would be the index view of the controller above. The js connects to the message broker and subscribes to \u003ccode\u003e/topic/hello\u003c/code\u003e.\n\nFor this example, I added a button allowing to trigger a send/receive roundtrip.\n\nWhile this example shows jquery used with the asset-pipeline plugin, the use of jquery is **not required**.\n\n### Service (brokerMessagingTemplate bean)\n\nTo send messages directly, the `brokerMessagingTemplate` bean (of type `SimpMessageSendingOperations`) can be used.\n\nThe plugin provides a `WebSocket` trait that autowires the `brokerMessagingTemplate` and delegates to it.\n\nThat `WebSocket` trait is automatically implemented by `WebSocket` artefacts, but you can implement it from other beans as well, e.g. from a service.\n\n*/grails-app/services/example/ExampleService.groovy*:\n\n```groovy\npackage example\n\nimport grails.plugin.springwebsocket.WebSocket\n\nclass ExampleService implements WebSocket {\n\n    void hello() {\n        convertAndSend(\"/topic/hello\", \"hello from service!\")\n    }\n\n}\n```\n\nOr, if you prefer, you can also inject and use the `brokerMessagingTemplate` bean directly.\n\n*/grails-app/services/example/ExampleService.groovy*:\n\n```groovy\npackage example\n\nimport org.springframework.messaging.simp.SimpMessageSendingOperations\n\nclass ExampleService {\n\n    SimpMessageSendingOperations brokerMessagingTemplate\n\n    void hello() {\n        brokerMessagingTemplate.convertAndSend(\"/topic/hello\", \"hello from service!\")\n    }\n\n}\n```\n\n## Configuration\n\nConfiguration relies on Spring java config, especially `@EnableWebSocketMessageBroker`.\n\n### Default Configuration\n\nBy default, a configuration bean named `webSocketConfig` of type `grails.plugin.springwebsocket.DefaultWebSocketConfig` is registered:\n\n* An in-memory `Map`-based message broker implementation is used\n* The prefixes for broker destinations (\"outgoing messages\") are: `/queue` or `/topic`\n* The prefix for application destinations (\"incoming messages\") is: `/app`\n* The stomp-endpoint URI is: `/stomp`\n* A `GrailsSimpAnnotationMethodMessageHandler` bean is defined to allow Grails controller methods to act as message handlers\n* A `GrailsWebSocketAnnotationMethodMessageHandler` bean is defined to allow Grails webSocket methods to act as message handlers\n\nIf the default values are fine for your application, you are good to go. No further configuration required then.\n\n### Custom Configuration\n\nThe default configuration can be customized/overridden by providing a `@Configuration` bean named `webSocketConfig`.\n\nAs a starting point, you can take a look at `DefaultWebSocketConfig` or you can create a config class/bean resembling the default config with:\n\n    ./grailsw create-web-socket-config my.package.name.MyClassName\n\nThat class will be placed under `src/main/groovy` and needs to be registered as a Spring configuration bean named `webSocketConfig`.\n\nThat can be accomplished in different ways, depending on your project and preferences, e.g.:\n\n* By making sure the class is in a package covered by `@ComponentScan`\n* Or, by adding `@Import(MyClassName)` to your `Application` class\n\nAs an alternative, you can disable the `WebSocketAutoConfiguration` explicitly and use a custom config with a different name or register it via the grails spring bean dsl: \n\n* Add `@EnableAutoConfiguration(exclude = WebSocketAutoConfiguration)` to your `Application` class\n* Or, configure `spring.autoconfigure.exclude=grails.plugin.springwebsocket.WebSocketAutoConfiguration`\n  \n*/grails-app/conf/spring/resources.groovy*:\n  \n  ```groovy\n  beans = {\n      webSocketConfig(my.package.name.MyClassName)\n  }\n  ```\n\nCheck the Spring docs/apis/samples for the available configuration options.\n\n### Full-Featured Broker\n\nTo use a full-featured (e.g. RabbitMQ, ActiveMQ, etc.) instead of the default simple broker, please refer to the Spring docs regarding configuration.\nAdditionally, add a dependency for TCP connection management.\n\n    implementation platform(\"io.projectreactor:reactor-bom:2024.0.8\")\n    implementation \"io.projectreactor.netty:reactor-netty\"\n\nIt is a good idea to align the BOM version with the one your current spring-boot BOM is using.\n\n## User Destinations\n\nTo send messages to specific users, you can (among other ways) annotate message handler methods with `@SendToUser` and/or use the `SimpMessagingTemplate.convertAndSendToUser(...)` methods.\n\n*/grails-app/controllers/example/ExampleController.groovy*:\n\n```groovy\nclass ExampleController {\n\n    @MessageMapping(\"/hello\")\n    @SendToUser(\"/queue/hello\")\n    protected String hello(String world) {\n        return \"hello from controller, ${world}!\"\n    }\n\n}\n```\n\nTo receive messages for the above `/queue/hello` user destination, the js client would have to subscribe to `/user/queue/hello`.\n\nIf a user is not logged in, `@SendToUser` will still work and only the user who sent the ingoing message will receive the outgoing one returned by the method.\n\n*/grails-app/services/example/ExampleService.groovy*:\n\n```groovy\nclass ExampleService implements WebSocket {\n\n    void hello() {\n        convertAndSendToUser(\"myTargetUsername\", \"/queue/hello\", \"hello, target user!\")\n    }\n\n}\n```\n\nAgain, to receive messages for the above `/queue/hello` user destination, the js client would have to subscribe to `/user/queue/hello`.\n\n## Security\n\nTo secure websocket messaging, we can leverage the first-class websocket security support of Spring Security 4.0+.\n\nCheck the Spring Security docs and the Spring Guides to get a jump-start into the topic.\n\nThere is a variety of options how to build your solution, including:\n* Securing message handler methods in a declarative fashion using annotations (e.g. `@PreAuthorize`)\n* Securing message handler methods by using an `@AuthenticationPrincipal`-annotated argument.\n* Filtering messages and subscriptions (e.g. with an `SecurityWebSocketMessageBrokerConfigurer`)\n\nI will only show a short example of securing message handler methods with security annotations and filtering inbound messages. I hope you do not mind the lack of import statements in the following code snippets ;)\n\nA working Spring Security setup is required. For the sake of brevity, here a super-minimalistic Spring Security dummy configuration:\n\n*/build.gradle*:\n\n```groovy\ndependencies {\n    implementation \"org.springframework.security:spring-security-config\"\n    implementation \"org.springframework.security:spring-security-messaging\"\n    implementation \"org.springframework.security:spring-security-web\"\n}\n```\n\n*/src/main/groovy/example/WebSecurityConfig.groovy*:\n\n```groovy\n@Configuration\n@EnableGlobalMethodSecurity(prePostEnabled = true)\n@EnableWebSecurity\nclass WebSecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http.httpBasic()\n        http.authorizeRequests().anyRequest().authenticated()\n    }\n\n    @Autowired\n    void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {\n        auth.inMemoryAuthentication()\n            .withUser(\"user\").password(\"password\").roles(\"USER\")\n    }\n\n}\n```\n\nSpring security will by default enable CSRF protection for websocket messages.\n\nTo include the required token in the stomp headers, your js code could look like this:\n\n*/grails-app/views/example/index.gsp*:\n\n```javascript\n$(function() {\n    var url = \"ws${createLink(uri: '/stomp', absolute: true).replaceFirst('(?i)http', '')}\"\n    var csrfHeaderName = \"${request._csrf.headerName}\";\n    var csrfToken = \"${request._csrf.token}\";\n    var client = new StompJs.Client({\n        brokerURL: url,\n        connectHeaders: {\n            [csrfHeaderName]: csrfToken\n        },\n        onConnect: () =\u003e {\n            // subscriptions etc. [...]\n        },\n    });\n    client.activate();\n});\n```\n\nThere are still embedded GSP GString expressions present, which means that snippet will only work in a GSP as-is. If you plan on extracting the js properly into an own js file (or similar), you will have to pass those values along.\n\n### Securing Message Handler Methods\n\nSecuring message handler methods can be achieved with annotations in a declarative fashion.\n\nThe following example shows a Grails controller with a secured message handler method and an message exception handler method.\n\n*/grails-app/controllers/example/ExampleController.groovy*:\n\n```groovy\nclass ExampleController {\n\n    @ControllerMethod\n    @MessageMapping(\"/hello\")\n    @PreAuthorize(\"hasRole('USER')\")\n    @SendTo(\"/topic/hello\")\n    String hello(String world) {\n        return \"hello from secured controller, ${world}!\"\n    }\n    \n    @ControllerMethod\n    @MessageExceptionHandler\n    @SendToUser(value = \"/queue/errors\", broadcast = false)\n    String handleException(Exception e) {\n        return \"caught ${e.message}\"\n    }\n    \n}\n```\n\nBesides the security handling itself, this snippet shows one important catch: if you want to secure Grails controller actions with `@PreAuthorize`, the secured method has to be public. However, as we still do not want the method to be exposed as a controller action but only as message handler, in this case the use of `@ControllerMethod` is required.  \n\nIf you use Grails `WebSocket` artefacts or Spring `@Controller` beans as message handlers, you do obviously not require those additional `@ControllerMethod` annotations.\n\n### Filtering messages\n\nThe following example shows how you can filter inbound messages by type and/or by destination pattern.\n\n*/src/main/groovy/example/WebSecurityConfig.groovy*:\n\n```groovy\n@Configuration\nclass WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {\n    \n    @Override\n    void configureInbound(MessageSecurityMetadataSourceRegistry messages) {\n        messages\n            .nullDestMatcher().authenticated()\n            .simpSubscribeDestMatchers(\"/user/queue/errors\").permitAll()\n            .simpDestMatchers(\"/app/**\").hasRole(\"USER\")\n            .simpSubscribeDestMatchers(\"/user/**\", \"/topic/**\").hasRole(\"USER\")\n            .simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()\n            .anyMessage().denyAll()\n    }\n    \n}\n```\n\n## Event Handling\n\nStarting with Grails 3, grails-plugin-events is a core plugin allowing to use the Reactor framework for event handling.\n\nWhile there is no special event integration regarding websocket messaging (because it is not really necessary anymore), a service that handles application events can look like the follwing snippet. I am _not_ talking about Spring `ApplicationEvent`s here, but Reactor `Event`s.\n\n*/grails-app/services/example/ExampleService.groovy*:\n\n```groovy\n@Consumer\nclass ExampleService implements WebSocket {\n    \n    @Selector(\"myEvent\")\n    void hello(Event\u003cString\u003e event) {\n        convertAndSend(\"/topic/myEventTopic\", \"myEvent: ${event.data}\")\n    }\n    \n}\n```\n\nEvents can be fired/sent from all application artefacts/beans that implement the trait `Events`. Grails service beans do so by convention. Those beans also allow dynamic registration of event listeners. E.g.:\n\n*/grails-app/services/example/ExampleService.groovy*:\n\n```groovy\nclass ExampleService {\n    \n    void fireMyEvent() {\n        notify(\"myEvent\", \"hello from myEvent!\")\n    }\n    \n}\n```\n\n*/grails-app/init/BootStrap.groovy*:\n\n```groovy\nclass BootStrap implements Events, WebSocket {\n\n    def init = {\n        on(\"myEvent\") { Event\u003cString\u003e event -\u003e\n            convertAndSend(\"/topic/myEventTopic\", \"myEvent: ${event.data}\")\n        }\n    }\n\n}\n```\n\nFor further information check the Grails async docs.\n\n## Misc\n\n### Startup performance\n\nScanning Grails controllers for message handler methods can impact application startup time if you have many controllers.\n\nOne way around this is to put your message handler methods into Grails `WebSocket` artefacts instead of Grails controllers and then use a custom websocket config class without the `GrailsSimpAnnotationMethodMessageHandler`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzyro23%2Fgrails-spring-websocket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzyro23%2Fgrails-spring-websocket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzyro23%2Fgrails-spring-websocket/lists"}