https://github.com/kacperfaber/wsl
💎WebSocket MVC framework, based uppon Spring to create WebSocket servers with many endpoints and with an amazing extensions system 💎
https://github.com/kacperfaber/wsl
cicd framework gradle kotlin maven mvc spring websocket ws
Last synced: 12 days ago
JSON representation
💎WebSocket MVC framework, based uppon Spring to create WebSocket servers with many endpoints and with an amazing extensions system 💎
- Host: GitHub
- URL: https://github.com/kacperfaber/wsl
- Owner: kacperfaber
- License: mit
- Created: 2022-09-28T10:57:59.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2023-09-25T11:24:18.000Z (over 2 years ago)
- Last Synced: 2025-03-23T01:35:20.459Z (about 1 year ago)
- Topics: cicd, framework, gradle, kotlin, maven, mvc, spring, websocket, ws
- Language: Kotlin
- Homepage: https://www.github.com/kacperfaber/wsl
- Size: 305 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# wsl
WebSocket MVC framework based on Spring.
[](https://github.com/kacperfaber/wsl/actions/workflows/build.yml)
[](https://github.com/kacperfaber/wsl/actions/workflows/test.yml)
[](https://github.com/kacperfaber/wsl/actions/workflows/publish.yml)
### Functionality
* Allows to create many well-separated endpoints inside one app.
* Create custom extensions as annotations to do anything you want.
* Create custom message formatter.
* Based on Spring with all features that spring provides.
* Open-Source
### Power of extensions
* Extensions are a powerful way to do anything you want.
* Actions will override extensions declared in upper level - Controller, handler or entire app
* You can add custom value to action invocation
* In extensions you have access to current WebSocketSession.
* Extensions are grouped in 3 categories
* Pre: Will run before action is called.
* Post: After action is called - you can use value that action returned.
* Parameter: Assigned to parameter in action, you can set parameter value.
## Installation
### Installing (using Gradle)
First add repository
```groovy
maven {
url = uri("https://maven.pkg.github.com/kacperfaber/wsl")
}
```
```groovy
// build.gradle
implementation "io.wsl:wsl:1.0.0-beta.1"
// build.gradle.kts
implementation("io.wsl:wsl:1.0.0-beta.1")
```
### Installing (using Maven)
First add repository.
```xml
https://maven.pkg.github.com/kacperfaber/wsl
```
```xml
io.wsl
wsl
1.0.0-beta.1
```
## Tutorial
#### 1. Create GlobalConfig
Create a class annotated with ```@GlobalConfig``` annotation.
You can define a prefix to scan using `@ScanPackage(prefix="")`
```kotlin
@GlobalConfig
@ScanPackage("io.testproj")
class MyGlobalConfig
```
##### 1.1 Create GlobalConfigClasses bean
```kotlin
@Bean
open fun classes(): GlobalConfigClass {
return GlobalConfigClass(MyGlobalConfig::class.java)
}
```
#### 2. Create a handler
Handler is a representation of endpoint. In **WSL** you can create many handlers, with many separated actions.
```kotlin
// this handler will be used with connection
// to /chat endpoint.
@Handler("/chat", allowedOrigins = ["*", "localhost"])
class ChatHandler
```
#### 3. Create controllers
Controllers in **WSL** are like in HTTP framework but they have no prefixes. **SocketController**'s have no prefixes,
but contain an actions.
Generally it's a spring components, so we can use all the spring container features we want.
```kotlin
@SocketController
@Component
@SetHandler() // see 4.
class ChatController {
@SocketAction("print_hello")
fun sayHello() {
println("Hello")
}
}
```
> Remember to use **@Component** from spring, or any other way to register this object in spring container.
#### 4. Assign controller to handler
All controllers must be assigned to handler. They're 2 ways to do this...
##### 4.1. Using @SetHandler
```kotlin
// will assign ExampleController to ChatHandler
@SetHandler(ChatHandler::class)
@SocketController
@Component
class ExampleController
```
##### 4.2 Using @DefaultHandler
**@DefaultHandler** annotation present on **Handler** will set the handler is default, and will be used when *
*Controller** has not @SetHandler annotation.
```kotlin
@Handler(/* ... */)
@DefaultHandler
class ChatHandler
```
##### 4.3 Exceptions
```kotlin
package io.wsl.exceptions
// When DefaultHandler is wanted, but not set.
// is wanted when the @SetHandler not set.
class DefaultHandlerNotSet
// Something wrong with the handler assigned.
class HandlerNotResolved
```
#### 5. Create extensions
**WSL** has nothing to use, but you can get awesome results using extensions.
Extensions are spring components related with annotations, **WSL** is calling extensions when message is received and
action is picked.
##### 5.1 PreExtension
Extension built to invoke before action (**@SocketAction**) is invoked.
```kotlin
@SetComponent(UseBodyComponent::class)
@SetExtensionKing(ExtensionKind.PreAction)
annotation class UseBody
@Component
class UseBodyComponent : PreExtension() {
override fun beforeInvoke(actionCall: ActionCall, annotation: Annotation, session: WebSocketSession) {
val body = Json().parse(actionCall.messageData)
actionCall.parameters[Body::class.java] = body
}
}
```
Usage:
```kotlin
@SocketController
class ChatController {
@SocketAction("print_body")
@UseBody
fun printBody(body: Body) {
println(body)
}
// Our component will add optional instance before request,
// which wsl use unless method takes.
// In this scenario we read the data from the message,
// But we can do many things.
}
```
##### 5.2 PostExtension
Post Extensions will be invoked after the action (Method) is called.
So, we can use what this method returned, and for example return this to the user.
```kotlin
@SetComponent(ReturnComponent::class)
@SetExtensionKing(ExtensionKind.PostAction)
annotation class Returns(val produces: String)
class ReturnsComponent() : PostExtension() {
override fun afterInvoke(actionCall: ActionCall, result: Any?, annotation: Annotation, session: WebSocketSession) {
// we're using annotation instance to get some data.
val produces = (annotation as Returns).produces
val responseBody = Json().write(result)
// send message to the user.
session.send(TextMessage("$produces: $responseBody"))
}
}
```
Usage:
```kotlin
@SocketController
class ChatController {
@SocketAction("my_name")
@Returns("your_name_is")
fun whatsMyName(): String = "Kacper"
// Now this action will respond with "Kacper" always.
// And this message will be send
// to user who sent message 'my_name'
}
```
> Remember, the built-in way to send messages is dependency MessagingService ( ***from io.wsl.messages.messaging*** ).
>
> You can access this just like normal bean.
>
##### 5.3 ParameterExtension
Extension we use in parameters, these kinda extensions only set the property before action is invoked.
```kotlin
@SetComponent(UseBodyComponent::class)
@SetExtensionKing(ExtensionKind.ActionParameter)
annotation class PrincipalId
@Component
class PrincipalIdComponent : ParameterExtension() {
override fun getValue(
actionCall: ActionCall,
parameterType: Class<*>,
annotation: Annotation,
session: WebSocketSession
): Any? {
// We're using WebSocketSession instance to get principal.name
return session.getPrincipal().getName()
}
}
```
Usage:
```kotlin
@SocketController
class ChatController {
@SocketAction("who_am_i")
fun whoAmI(@PrincipalId name: String) {
// `name` is equal to principal id.
}
}
```
##### 5.4 Inheritance
**WSL** applies **PostExtension**s and **PreExtensions** to lower floors of WSL structure.
1. **GlobalConfig**:
Extension applied in GlobalConfig will be in all **WSL** structure.
2. **Handler**: Extension applied in Handler will be used in all controllers [then in actions], in whole handler.
3. **SocketController**: Will apply extension to all SocketAction`s in this controller.
Some example I wrote in free style
```kotlin
@GlobalConfig
@A
class Global
@Handler
@B
// @A: ChatHandler derives @A extension from GlobalConfig
class ChatHandler
@SocketController
@D
// @A: From Global
// @B: From ChatHandler
class ChatController
@SocketAction
@E
// @A: From GlobalConfig
// @B: From ChatHandler
fun sendMessage()
// Finally: we have:
// @A, @B, @D, @E
@Handler
@C
// @A: ChatHandler derives @A extension from GlobalConfig
class UserHandler
@SocketController
@Z
// @C: LoginController derives @A extension from GlobalConfig
// @C: LoginController derives @C extension from UserHandler
class LoginController
// Finally: All actions under LoginController have @A, @C, @Z
```
#### 6. Register handlers
```kotlin
// Will simplify this in update.
@Configuration
@EnableWebSocket
@Import(WslSpringConfig::class)
class ProjectConfigurer(
val actionsByHandlerGrouper: ActionsByHandlerGrouper,
val handlerFactory: WebSocketHandlerFactory,
val wslModel: WslModel
) : WebSocketConfigurer {
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
val actionsGrouped = actionsByHandlerGrouper.group(wslModel.actions, wslModel.controllers, wslModel.handlers)
wslModel.handlers.forEach {
val handler = handlerFactory.createHandler(
actionsGrouped[it] ?: throw Exception("No Actions depends to handler " + it.clazz), it
)
registry.addHandler(handler, it.path).setAllowedOriginPatterns(*it.allowedOrigins)
}
}
}
```
## 7. Warning
It's just an beta version, please report all issues.