{"id":26573946,"url":"https://github.com/kacperfaber/wsl","last_synced_at":"2026-04-18T04:31:45.216Z","repository":{"id":196481976,"uuid":"542543942","full_name":"kacperfaber/wsl","owner":"kacperfaber","description":"💎WebSocket MVC framework, based uppon Spring to create WebSocket servers with many endpoints and with an amazing extensions system 💎","archived":false,"fork":false,"pushed_at":"2023-09-25T11:24:18.000Z","size":312,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-23T01:35:20.459Z","etag":null,"topics":["cicd","framework","gradle","kotlin","maven","mvc","spring","websocket","ws"],"latest_commit_sha":null,"homepage":"https://www.github.com/kacperfaber/wsl","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kacperfaber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-09-28T10:57:59.000Z","updated_at":"2023-09-25T11:23:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"3087bd8f-ab99-475b-a0d8-6ca99ac4be6e","html_url":"https://github.com/kacperfaber/wsl","commit_stats":null,"previous_names":["kacperfaber/wsl"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/kacperfaber/wsl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kacperfaber%2Fwsl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kacperfaber%2Fwsl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kacperfaber%2Fwsl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kacperfaber%2Fwsl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kacperfaber","download_url":"https://codeload.github.com/kacperfaber/wsl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kacperfaber%2Fwsl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31956820,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"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":["cicd","framework","gradle","kotlin","maven","mvc","spring","websocket","ws"],"created_at":"2025-03-23T01:31:43.068Z","updated_at":"2026-04-18T04:31:45.196Z","avatar_url":"https://github.com/kacperfaber.png","language":"Kotlin","readme":"# wsl\n\nWebSocket MVC framework based on Spring.\n\n[![build](https://github.com/kacperfaber/wsl/actions/workflows/build.yml/badge.svg)](https://github.com/kacperfaber/wsl/actions/workflows/build.yml)\n[![test](https://github.com/kacperfaber/wsl/actions/workflows/test.yml/badge.svg)](https://github.com/kacperfaber/wsl/actions/workflows/test.yml)\n[![publish](https://github.com/kacperfaber/wsl/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/kacperfaber/wsl/actions/workflows/publish.yml)\n\n### Functionality\n\n* Allows to create many well-separated endpoints inside one app.\n* Create custom extensions as annotations to do anything you want.\n* Create custom message formatter.\n* Based on Spring with all features that spring provides.\n* Open-Source\n\n### Power of extensions\n* Extensions are a powerful way to do anything you want.\n* Actions will override extensions declared in upper level - Controller, handler or entire app\n* You can add custom value to action invocation\n* In extensions you have access to current WebSocketSession.\n* Extensions are grouped in 3 categories\n  * Pre: Will run before action is called.\n  * Post: After action is called - you can use value that action returned.\n  * Parameter: Assigned to parameter in action, you can set parameter value.\n\n\n## Installation\n\n### Installing (using Gradle)\n\nFirst add repository\n\n```groovy\nmaven {\n    url = uri(\"https://maven.pkg.github.com/kacperfaber/wsl\")\n}\n```\n\n```groovy\n// build.gradle\nimplementation \"io.wsl:wsl:1.0.0-beta.1\"\n\n// build.gradle.kts\nimplementation(\"io.wsl:wsl:1.0.0-beta.1\")\n\n```\n\n### Installing (using Maven)\n\nFirst add repository.\n\n```xml\n\n\u003crepository\u003e\n    \u003curl\u003ehttps://maven.pkg.github.com/kacperfaber/wsl\u003c/url\u003e\n\u003c/repository\u003e\n```\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.wsl\u003c/groupId\u003e\n    \u003cartifactId\u003ewsl\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0-beta.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Tutorial\n\n#### 1. Create GlobalConfig\n\nCreate a class annotated with ```@GlobalConfig``` annotation.\nYou can define a prefix to scan using `@ScanPackage(prefix=\"\")`\n\n```kotlin\n@GlobalConfig\n@ScanPackage(\"io.testproj\")\nclass MyGlobalConfig\n```\n\n##### 1.1 Create GlobalConfigClasses bean\n\n```kotlin\n@Bean\nopen fun classes(): GlobalConfigClass {\n    return GlobalConfigClass(MyGlobalConfig::class.java)\n}\n```\n\n#### 2. Create a handler\n\nHandler is a representation of endpoint. In **WSL** you can create many handlers, with many separated actions.\n\n```kotlin\n// this handler will be used with connection\n// to /chat endpoint.\n\n@Handler(\"/chat\", allowedOrigins = [\"*\", \"localhost\"])\nclass ChatHandler \n```\n\n#### 3. Create controllers\n\nControllers in **WSL** are like in HTTP framework but they have no prefixes. **SocketController**'s have no prefixes,\nbut contain an actions.\nGenerally it's a spring components, so we can use all the spring container features we want.\n\n```kotlin\n@SocketController\n@Component\n@SetHandler() // see 4.\nclass ChatController {\n    @SocketAction(\"print_hello\")\n    fun sayHello() {\n        println(\"Hello\")\n    }\n}\n```\n\n\u003e Remember to use **@Component** from spring, or any other way to register this object in spring container.\n\n#### 4. Assign controller to handler\n\nAll controllers must be assigned to handler. They're 2 ways to do this...\n\n##### 4.1. Using @SetHandler\n\n```kotlin\n// will assign ExampleController to ChatHandler\n@SetHandler(ChatHandler::class)\n@SocketController\n@Component\nclass ExampleController\n```\n\n##### 4.2 Using @DefaultHandler\n\n**@DefaultHandler** annotation present on **Handler** will set the handler is default, and will be used when *\n*Controller** has not @SetHandler annotation.\n\n```kotlin\n@Handler(/* ... */)\n@DefaultHandler\nclass ChatHandler\n```\n\n##### 4.3 Exceptions\n\n```kotlin\npackage io.wsl.exceptions\n\n// When DefaultHandler is wanted, but not set.\n// is wanted when the @SetHandler not set.\nclass DefaultHandlerNotSet\n\n// Something wrong with the handler assigned.\nclass HandlerNotResolved\n```\n\n#### 5. Create extensions\n\n**WSL** has nothing to use, but you can get awesome results using extensions.\nExtensions are spring components related with annotations, **WSL** is calling extensions when message is received and\naction is picked.\n\n##### 5.1 PreExtension\n\nExtension built to invoke before action (**@SocketAction**) is invoked.\n\n```kotlin\n@SetComponent(UseBodyComponent::class)\n@SetExtensionKing(ExtensionKind.PreAction)\nannotation class UseBody\n\n@Component\nclass UseBodyComponent : PreExtension() {\n    override fun beforeInvoke(actionCall: ActionCall, annotation: Annotation, session: WebSocketSession) {\n        val body = Json().parse\u003cBody\u003e(actionCall.messageData)\n        actionCall.parameters[Body::class.java] = body\n    }\n}\n```\n\nUsage:\n\n```kotlin\n@SocketController\nclass ChatController {\n    @SocketAction(\"print_body\")\n    @UseBody\n    fun printBody(body: Body) {\n        println(body)\n    }\n\n    // Our component will add optional instance before request,\n    // which wsl use unless method takes.\n\n    // In this scenario we read the data from the message,\n    // But we can do many things.\n}\n```\n\n##### 5.2 PostExtension\n\nPost Extensions will be invoked after the action (Method) is called.\nSo, we can use what this method returned, and for example return this to the user.\n\n```kotlin\n@SetComponent(ReturnComponent::class)\n@SetExtensionKing(ExtensionKind.PostAction)\nannotation class Returns(val produces: String)\n\nclass ReturnsComponent() : PostExtension() {\n    override fun afterInvoke(actionCall: ActionCall, result: Any?, annotation: Annotation, session: WebSocketSession) {\n        // we're using annotation instance to get some data. \n        val produces = (annotation as Returns).produces\n\n        val responseBody = Json().write(result)\n\n        // send message to the user.\n        session.send(TextMessage(\"$produces: $responseBody\"))\n    }\n}\n```\n\nUsage:\n\n```kotlin\n@SocketController\nclass ChatController {\n    @SocketAction(\"my_name\")\n    @Returns(\"your_name_is\")\n    fun whatsMyName(): String = \"Kacper\"\n\n    // Now this action will respond with \"Kacper\" always.\n    // And this message will be send\n    // to user who sent message 'my_name'\n}\n```\n\n\u003e Remember, the built-in way to send messages is dependency MessagingService ( ***from io.wsl.messages.messaging*** ).\n\u003e\n\u003e You can access this just like normal bean.\n\u003e\n\n##### 5.3 ParameterExtension\n\nExtension we use in parameters, these kinda extensions only set the property before action is invoked.\n\n```kotlin\n@SetComponent(UseBodyComponent::class)\n@SetExtensionKing(ExtensionKind.ActionParameter)\nannotation class PrincipalId\n\n@Component\nclass PrincipalIdComponent : ParameterExtension() {\n    override fun getValue(\n        actionCall: ActionCall,\n        parameterType: Class\u003c*\u003e,\n        annotation: Annotation,\n        session: WebSocketSession\n    ): Any? {\n        // We're using WebSocketSession instance to get principal.name\n        return session.getPrincipal().getName()\n    }\n}\n```\n\nUsage:\n\n```kotlin\n@SocketController\nclass ChatController {\n    @SocketAction(\"who_am_i\")\n    fun whoAmI(@PrincipalId name: String) {\n        // `name` is equal to principal id.\n    }\n\n}\n```\n\n##### 5.4 Inheritance\n\n**WSL** applies **PostExtension**s and **PreExtensions** to lower floors of WSL structure.\n\n1. **GlobalConfig**:\n   Extension applied in GlobalConfig will be in all **WSL** structure.\n\n2. **Handler**: Extension applied in Handler will be used in all controllers [then in actions], in whole handler.\n\n3. **SocketController**: Will apply extension to all SocketAction`s in this controller.\n\nSome example I wrote in free style\n\n```kotlin\n\n@GlobalConfig\n@A\nclass Global\n\n@Handler\n@B\n// @A: ChatHandler derives @A extension from GlobalConfig\nclass ChatHandler\n\n@SocketController\n@D\n// @A: From Global\n// @B: From ChatHandler\nclass ChatController\n\n@SocketAction\n@E\n// @A: From GlobalConfig\n// @B: From ChatHandler\nfun sendMessage()\n\n// Finally: we have:\n// @A, @B, @D, @E\n\n@Handler\n@C\n// @A: ChatHandler derives @A extension from GlobalConfig\nclass UserHandler\n\n@SocketController\n@Z\n// @C: LoginController derives @A extension from GlobalConfig\n// @C: LoginController derives @C extension from UserHandler\nclass LoginController\n\n// Finally: All actions under LoginController have @A, @C, @Z\n\n```\n\n#### 6. Register handlers\n\n```kotlin\n// Will simplify this in update.\n\n@Configuration\n@EnableWebSocket\n@Import(WslSpringConfig::class)\nclass ProjectConfigurer(\n    val actionsByHandlerGrouper: ActionsByHandlerGrouper,\n    val handlerFactory: WebSocketHandlerFactory,\n    val wslModel: WslModel\n) : WebSocketConfigurer {\n    override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {\n        val actionsGrouped = actionsByHandlerGrouper.group(wslModel.actions, wslModel.controllers, wslModel.handlers)\n        wslModel.handlers.forEach {\n            val handler = handlerFactory.createHandler(\n                actionsGrouped[it] ?: throw Exception(\"No Actions depends to handler \" + it.clazz), it\n            )\n            registry.addHandler(handler, it.path).setAllowedOriginPatterns(*it.allowedOrigins)\n        }\n    }\n}\n```\n\n## 7. Warning\n\nIt's just an beta version, please report all issues.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkacperfaber%2Fwsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkacperfaber%2Fwsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkacperfaber%2Fwsl/lists"}