Added rsocket sample code
This commit is contained in:
parent
3c0692394d
commit
c047a903a5
.gitignore.prettierrcyarn.lock
frontend/src
package.jsonpom.xmlrsocket-commands.txtsrc/main
kotlin/com/example/demo
resources
6
.gitignore
vendored
6
.gitignore
vendored
@ -29,3 +29,9 @@ build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
node_modules
|
||||
|
||||
yarn-error.log
|
||||
|
||||
dist/
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"printWidth": 80
|
||||
}
|
9
frontend/src/EchoResponder.ts
Normal file
9
frontend/src/EchoResponder.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export class EchoResponder {
|
||||
callback: any;
|
||||
constructor(callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
fireAndForget(payload) {
|
||||
this.callback(payload);
|
||||
}
|
||||
}
|
130
frontend/src/main.ts
Normal file
130
frontend/src/main.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import {
|
||||
RSocketClient,
|
||||
JsonSerializer,
|
||||
IdentitySerializer,
|
||||
Encodable,
|
||||
encodeAndAddWellKnownMetadata,
|
||||
encodeAndAddCustomMetadata,
|
||||
MESSAGE_RSOCKET_ROUTING,
|
||||
MESSAGE_RSOCKET_COMPOSITE_METADATA,
|
||||
TEXT_PLAIN,
|
||||
WellKnownMimeType,
|
||||
APPLICATION_JSON,
|
||||
} from "rsocket-core";
|
||||
import { EchoResponder } from "./EchoResponder";
|
||||
import { every } from "rsocket-flowable";
|
||||
import RSocketTcpClient from "rsocket-tcp-client";
|
||||
import RSocketWebSocketClient from "rsocket-websocket-client";
|
||||
|
||||
const maxRSocketRequestN = 2147483647;
|
||||
const host = "127.0.0.1";
|
||||
const port = 7000;
|
||||
const keepAlive = 60000;
|
||||
const lifetime = 180000;
|
||||
const dataMimeType = "application/octet-stream";
|
||||
const metadataMimeType = MESSAGE_RSOCKET_COMPOSITE_METADATA.string;
|
||||
|
||||
const address = { host: "localhost", port: 7000 };
|
||||
|
||||
const messageReceiver = (payload) => {
|
||||
//do what you want to do with received message
|
||||
if ((payload.metadata as string).slice(1) == "user.queue.reply") console.log("YES");
|
||||
else console.log("No");
|
||||
console.log((payload.metadata as string).slice(1))
|
||||
console.log(payload);
|
||||
};
|
||||
const responder = new EchoResponder(messageReceiver);
|
||||
|
||||
function getClientTransport(host: string, port: number) {
|
||||
return new RSocketWebSocketClient({
|
||||
url: "ws://localhost:7000/client-id",
|
||||
});
|
||||
}
|
||||
|
||||
interface Message {
|
||||
toUser: string;
|
||||
fromUser: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const client = new RSocketClient({
|
||||
// send/receive JSON objects instead of strings/buffers
|
||||
serializers: {
|
||||
data: JsonSerializer,
|
||||
metadata: IdentitySerializer,
|
||||
},
|
||||
setup: {
|
||||
//for connection mapping on server
|
||||
payload: {
|
||||
data: "1234",
|
||||
metadata: String.fromCharCode("client-id".length) + "client-id",
|
||||
},
|
||||
// ms btw sending keepalive to server
|
||||
keepAlive: 60000,
|
||||
|
||||
// ms timeout if no keepalive response
|
||||
lifetime: 180000,
|
||||
|
||||
// format of `data`
|
||||
dataMimeType: "application/json",
|
||||
|
||||
// format of `metadata`
|
||||
metadataMimeType: "message/x.rsocket.routing.v0",
|
||||
},
|
||||
responder: responder,
|
||||
transport: getClientTransport(address.host, address.port),
|
||||
});
|
||||
const route = "user.queue.reply";
|
||||
const sendRoute = "private.news";
|
||||
client.connect().subscribe({
|
||||
onComplete: (rSocket) => {
|
||||
every(1000).subscribe({
|
||||
onNext: (time) => {
|
||||
console.log(`Requester availability: ${rSocket.availability()}`);
|
||||
// rSocket
|
||||
// .requestResponse({
|
||||
// data: time.toString(),
|
||||
// metadata: "",
|
||||
// })
|
||||
// .subscribe({
|
||||
// onComplete: (response) => {
|
||||
// const data = response.data;
|
||||
// if (data) {
|
||||
// console.log(`Requester response: ${data}`);
|
||||
// }
|
||||
// },
|
||||
// onError: (error) =>
|
||||
// console.log(`Requester error: ${error.message}`),
|
||||
// });
|
||||
|
||||
// rSocket
|
||||
// .requestStream({
|
||||
// metadata: String.fromCharCode(route.length) + route,
|
||||
// })
|
||||
// .subscribe({
|
||||
// onComplete: () => console.log("Request-stream completed"),
|
||||
// onError: (error) =>
|
||||
// console.error(`Request-stream error:${error.message}`),
|
||||
// onNext: (value) => console.log("%s", value.data),
|
||||
// onSubscribe: (sub) => sub.request(maxRSocketRequestN),
|
||||
// });
|
||||
|
||||
rSocket.fireAndForget({
|
||||
data: { toUser: "4567", fromUser: "1234", message: "testHello" },
|
||||
metadata: String.fromCharCode(sendRoute.length) + sendRoute,
|
||||
});
|
||||
// S APPLICATION_JSON.string
|
||||
},
|
||||
onSubscribe: (subscription) =>
|
||||
subscription.request(Number.MAX_SAFE_INTEGER),
|
||||
});
|
||||
console.log("RSocket completed");
|
||||
|
||||
rSocket.connectionStatus().subscribe((status) => {
|
||||
console.log("Connection status:", status);
|
||||
});
|
||||
},
|
||||
onError: (error) => console.log(`RSocket error: ${error.message}`),
|
||||
});
|
||||
|
||||
// setTimeout(() => {}, 360000);
|
28
package.json
Normal file
28
package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.1",
|
||||
"main": "index.js",
|
||||
"repository": "https://git.arcusiridis.com/nova/Async-Spring-Kotlin-JOOQ-Demo.git",
|
||||
"author": "Rohan Sircar <rohansircar@tuta.io>",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@types/rsocket-core": "^0.0.5",
|
||||
"@types/rsocket-flowable": "^0.0.5",
|
||||
"@types/rsocket-tcp-client": "^0.0.1",
|
||||
"@types/rsocket-websocket-client": "^0.0.3",
|
||||
"rsocket-core": "^0.0.19",
|
||||
"rsocket-flowable": "^0.0.14",
|
||||
"rsocket-tcp-client": "^0.0.19",
|
||||
"rsocket-websocket-client": "^0.0.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^16.5.2",
|
||||
"tsify": "^5.0.0",
|
||||
"typescript": "^3.9.7",
|
||||
"watchify": "^3.11.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build" : "browserify frontend/src/main.ts --debug -p [ tsify ] -o src/main/resources/static/js/dist/bundle.js",
|
||||
"watch" : "watchify frontend/src/main.ts --debug -p [ tsify ] -o src/main/resources/static/js/dist/bundle.js"
|
||||
}
|
||||
}
|
14
pom.xml
14
pom.xml
@ -103,6 +103,20 @@
|
||||
<artifactId>spring-boot-starter-rsocket</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.vavr/vavr -->
|
||||
<dependency>
|
||||
<groupId>io.vavr</groupId>
|
||||
<artifactId>vavr</artifactId>
|
||||
<version>0.10.3</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.vavr/vavr-kotlin -->
|
||||
<dependency>
|
||||
<groupId>io.vavr</groupId>
|
||||
<artifactId>vavr-kotlin</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||
|
7
rsocket-commands.txt
Normal file
7
rsocket-commands.txt
Normal file
@ -0,0 +1,7 @@
|
||||
java -jar rsc.jar --debug --stream --route messages.findAll tcp://localhost:7000
|
||||
java -jar rsc.jar --debug --request --route users.John tcp://localhost:7000
|
||||
java -jar rsc.jar --debug --request --route users.Doe tcp://localhost:7000
|
||||
|
||||
java -jar rsc.jar --debug --request --data "{\"origin\":\"Client\",\"interaction\":\"Request\"}" --route request-response tcp://localhost:7000
|
||||
|
||||
java -jar rsc.jar --setup "{\"data\":\"1234\",\"metadata\":\"client\-id\"}" --route client-id tcp://localhost:7000
|
@ -1,73 +1,12 @@
|
||||
package com.example.demo.controller
|
||||
|
||||
import com.example.demo.model.User
|
||||
import com.example.demo.service.UserService
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.messaging.handler.annotation.DestinationVariable
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
|
||||
|
||||
@RestController
|
||||
@Lazy
|
||||
class HomeController(@Autowired @Lazy private val userService: UserService) {
|
||||
@Controller
|
||||
class HomeController {
|
||||
@GetMapping("/")
|
||||
fun index(): Mono<String> {
|
||||
return Mono.just("foo")
|
||||
fun home(): String {
|
||||
return "rsocket"
|
||||
}
|
||||
|
||||
@GetMapping("/data")
|
||||
fun dataHandler(): Mono<MyData> {
|
||||
return Mono.just(MyData(1, "hello2"))
|
||||
}
|
||||
|
||||
@GetMapping("/data2")
|
||||
suspend fun dataHandler2(): MyData = withContext(Dispatchers.IO) {
|
||||
delay(10_000)
|
||||
MyData(1, "hello3")
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
fun users(): Flux<User> {
|
||||
return userService.users()
|
||||
}
|
||||
|
||||
@GetMapping("/users2")
|
||||
suspend fun users2(): Flow<User> {
|
||||
return userService.users2()
|
||||
}
|
||||
|
||||
@GetMapping("/users3")
|
||||
suspend fun users3() = coroutineScope {
|
||||
val fun1 = async {
|
||||
userService.users2().toList()
|
||||
}
|
||||
|
||||
Pair(fun1.await(), 1)
|
||||
}
|
||||
|
||||
@GetMapping("/messages/{userName}")
|
||||
suspend fun messages(@PathVariable userName: String) = coroutineScope {
|
||||
userService.getUserMessages(userName)
|
||||
}
|
||||
|
||||
@MessageMapping("messages.findAll")
|
||||
suspend fun all() = coroutineScope {
|
||||
userService.getAllMessages()
|
||||
}
|
||||
|
||||
@MessageMapping("users.{name}")
|
||||
suspend fun getUser(@DestinationVariable name: String) = coroutineScope {
|
||||
userService.getUserByName(name)
|
||||
}
|
||||
}
|
||||
|
||||
data class MyData(val id: Int, val name2: String)
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.example.demo.controller
|
||||
|
||||
import com.example.demo.model.User
|
||||
import com.example.demo.service.UserService
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.messaging.handler.annotation.DestinationVariable
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
|
||||
|
||||
@RestController
|
||||
@Lazy
|
||||
@RequestMapping("/api")
|
||||
class HomeRestController(@Autowired @Lazy private val userService: UserService) {
|
||||
@GetMapping("/")
|
||||
fun index(): Mono<String> {
|
||||
return Mono.just("foo")
|
||||
}
|
||||
|
||||
@GetMapping("/data")
|
||||
fun dataHandler(): Mono<MyData> {
|
||||
return Mono.just(MyData(1, "hello2"))
|
||||
}
|
||||
|
||||
@GetMapping("/data2")
|
||||
suspend fun dataHandler2(): MyData = withContext(Dispatchers.IO) {
|
||||
delay(10_000)
|
||||
MyData(1, "hello3")
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
fun users(): Flux<User> {
|
||||
return userService.users()
|
||||
}
|
||||
|
||||
@GetMapping("/users2")
|
||||
suspend fun users2(): Flow<User> {
|
||||
return userService.users2()
|
||||
}
|
||||
|
||||
@GetMapping("/users3")
|
||||
suspend fun users3() = coroutineScope {
|
||||
val fun1 = async {
|
||||
userService.users2().toList()
|
||||
}
|
||||
|
||||
Pair(fun1.await(), 1)
|
||||
}
|
||||
|
||||
@GetMapping("/messages/{userName}")
|
||||
suspend fun messages(@PathVariable userName: String) = coroutineScope {
|
||||
userService.getUserMessages(userName)
|
||||
}
|
||||
|
||||
@MessageMapping("messages.findAll")
|
||||
suspend fun all() = coroutineScope {
|
||||
userService.getAllMessages()
|
||||
}
|
||||
|
||||
@MessageMapping("users.{name}")
|
||||
suspend fun getUser(@DestinationVariable name: String) = coroutineScope {
|
||||
userService.getUserByName(name)
|
||||
}
|
||||
}
|
||||
|
||||
data class MyData(val id: Int, val name2: String)
|
75
src/main/kotlin/com/example/demo/controller/RsocketClient.kt
Normal file
75
src/main/kotlin/com/example/demo/controller/RsocketClient.kt
Normal file
@ -0,0 +1,75 @@
|
||||
package com.example.demo.controller
|
||||
|
||||
import com.example.demo.model.Message
|
||||
import io.vavr.collection.HashMap
|
||||
import io.vavr.collection.Map
|
||||
import kotlinx.coroutines.delay
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||
import org.springframework.messaging.rsocket.RSocketRequester
|
||||
import org.springframework.messaging.rsocket.annotation.ConnectMapping
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.util.MimeType
|
||||
|
||||
@Controller
|
||||
class RSocketConnectionController {
|
||||
|
||||
private val log = LoggerFactory.getLogger(RSocketConnectionController::class.java)
|
||||
|
||||
private var requesterMap: Map<String, RSocketRequester> = HashMap.empty()
|
||||
|
||||
@Synchronized
|
||||
private fun getRequesterMap(): Map<String, RSocketRequester> {
|
||||
return requesterMap
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun addRequester(rSocketRequester: RSocketRequester, clientId: String) {
|
||||
log.info("adding requester {}", clientId)
|
||||
requesterMap = requesterMap.put(clientId, rSocketRequester)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun removeRequester(clientId: String) {
|
||||
log.info("removing requester {}", clientId)
|
||||
requesterMap = requesterMap.remove(clientId)
|
||||
}
|
||||
|
||||
@ConnectMapping("client-id")
|
||||
fun onConnect(rSocketRequester: RSocketRequester, clientId: String) {
|
||||
val clientIdFixed = clientId.replace("\"", "") //check why the serializer adds " to strings
|
||||
// rSocketRequester.rsocket().dispose() //to reject connection
|
||||
rSocketRequester
|
||||
.rsocket()
|
||||
.onClose()
|
||||
.subscribe(null, null, {
|
||||
log.info("{} just disconnected", clientIdFixed)
|
||||
removeRequester(clientIdFixed)
|
||||
})
|
||||
addRequester(rSocketRequester, clientIdFixed)
|
||||
}
|
||||
|
||||
@MessageMapping("private.news")
|
||||
fun privateNews(message: Message, rSocketRequesterParam: RSocketRequester) {
|
||||
getRequesterMap()
|
||||
.filterKeys { key -> key == message.toUser || key == message.fromUser }
|
||||
.values()
|
||||
.forEach { requester -> sendMessage(requester, message) }
|
||||
|
||||
}
|
||||
|
||||
@MessageExceptionHandler
|
||||
suspend fun handleException(ex: IllegalArgumentException): String {
|
||||
delay(10)
|
||||
return "${ex.message} handled"
|
||||
}
|
||||
|
||||
private fun sendMessage(requester: RSocketRequester, message: Message) =
|
||||
requester
|
||||
.route("user.queue.reply")
|
||||
.data(message)
|
||||
.send()
|
||||
.subscribe()
|
||||
|
||||
}
|
3
src/main/kotlin/com/example/demo/model/Message.kt
Normal file
3
src/main/kotlin/com/example/demo/model/Message.kt
Normal file
@ -0,0 +1,3 @@
|
||||
package com.example.demo.model
|
||||
|
||||
data class Message(val fromUser: String, val toUser: String, val message: String)
|
@ -7,4 +7,4 @@ spring.datasource.username=test_user
|
||||
spring.datasource.password=password
|
||||
|
||||
spring.rsocket.server.port=7000
|
||||
spring.rsocket.server.transport=tcp
|
||||
spring.rsocket.server.transport=websocket
|
15
src/main/resources/templates/rsocket.html
Normal file
15
src/main/resources/templates/rsocket.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Rsocket</title>
|
||||
</head>
|
||||
<script th:src="@{/js/dist/bundle.js}"></script>
|
||||
|
||||
<body>
|
||||
home
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user