Added rsocket sample code

This commit is contained in:
Rohan Sircar 2020-08-08 13:55:28 +05:30
parent 3c0692394d
commit c047a903a5
14 changed files with 2445 additions and 68 deletions

6
.gitignore vendored
View File

@ -29,3 +29,9 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
node_modules
yarn-error.log
dist/

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 80
}

View 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
View 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
View 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
View File

@ -103,6 +103,20 @@
<artifactId>spring-boot-starter-rsocket</artifactId> <artifactId>spring-boot-starter-rsocket</artifactId>
<scope>compile</scope> <scope>compile</scope>
</dependency> </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> </dependencies>
<build> <build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>

7
rsocket-commands.txt Normal file
View 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

View File

@ -1,73 +1,12 @@
package com.example.demo.controller package com.example.demo.controller
import com.example.demo.model.User import org.springframework.stereotype.Controller
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.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
@Controller
@RestController class HomeController {
@Lazy
class HomeController(@Autowired @Lazy private val userService: UserService) {
@GetMapping("/") @GetMapping("/")
fun index(): Mono<String> { fun home(): String {
return Mono.just("foo") 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)

View File

@ -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)

View 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()
}

View File

@ -0,0 +1,3 @@
package com.example.demo.model
data class Message(val fromUser: String, val toUser: String, val message: String)

View File

@ -7,4 +7,4 @@ spring.datasource.username=test_user
spring.datasource.password=password spring.datasource.password=password
spring.rsocket.server.port=7000 spring.rsocket.server.port=7000
spring.rsocket.server.transport=tcp spring.rsocket.server.transport=websocket

View 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>

2071
yarn.lock Normal file

File diff suppressed because it is too large Load Diff