Rohan Sircar
4 years ago
14 changed files with 2445 additions and 68 deletions
-
6.gitignore
-
5.prettierrc
-
9frontend/src/EchoResponder.ts
-
130frontend/src/main.ts
-
28package.json
-
14pom.xml
-
7rsocket-commands.txt
-
73src/main/kotlin/com/example/demo/controller/HomeController.kt
-
75src/main/kotlin/com/example/demo/controller/HomeRestController.kt
-
75src/main/kotlin/com/example/demo/controller/RsocketClient.kt
-
3src/main/kotlin/com/example/demo/model/Message.kt
-
2src/main/resources/application.properties
-
15src/main/resources/templates/rsocket.html
-
2071yarn.lock
@ -0,0 +1,5 @@ |
|||
{ |
|||
"tabWidth": 2, |
|||
"useTabs": false, |
|||
"printWidth": 80 |
|||
} |
@ -0,0 +1,9 @@ |
|||
export class EchoResponder { |
|||
callback: any; |
|||
constructor(callback) { |
|||
this.callback = callback; |
|||
} |
|||
fireAndForget(payload) { |
|||
this.callback(payload); |
|||
} |
|||
} |
@ -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);
|
@ -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" |
|||
} |
|||
} |
@ -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") |
|||
} |
|||
|
|||
@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") |
|||
fun home(): String { |
|||
return "rsocket" |
|||
} |
|||
|
|||
@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) |
@ -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() |
|||
|
|||
} |
@ -0,0 +1,3 @@ |
|||
package com.example.demo.model |
|||
|
|||
data class Message(val fromUser: String, val toUser: String, val message: String) |
@ -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
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue