websocket client now includes transport device to use in higher order protocols
This commit is contained in:
parent
4098358233
commit
9545ca28cf
@ -8,7 +8,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.4.5-SNAPSHOT"
|
version = "0.4.6-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -11,10 +11,7 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
|||||||
import kotlinx.coroutines.channels.ClosedSendChannelException
|
import kotlinx.coroutines.channels.ClosedSendChannelException
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.sergeych.crypto2.SigningKey
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.kiloparsec.KiloClient
|
import net.sergeych.kiloparsec.*
|
||||||
import net.sergeych.kiloparsec.KiloConnectionData
|
|
||||||
import net.sergeych.kiloparsec.KiloInterface
|
|
||||||
import net.sergeych.kiloparsec.RemoteInterface
|
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.exception
|
import net.sergeych.mp_logger.exception
|
||||||
import net.sergeych.mp_logger.info
|
import net.sergeych.mp_logger.info
|
||||||
@ -24,91 +21,106 @@ import net.sergeych.tools.AtomicCounter
|
|||||||
|
|
||||||
private val counter = AtomicCounter()
|
private val counter = AtomicCounter()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut to create websocket client. Use [webSocketTransportDevice] with [KiloClient]
|
||||||
|
* for fine-grained control.
|
||||||
|
*/
|
||||||
fun <S> websocketClient(
|
fun <S> websocketClient(
|
||||||
path: String,
|
path: String,
|
||||||
clientInterface: KiloInterface<S> = KiloInterface(),
|
clientInterface: KiloInterface<S> = KiloInterface(),
|
||||||
client: HttpClient = HttpClient { install(WebSockets) },
|
|
||||||
secretKey: SigningKey? = null,
|
secretKey: SigningKey? = null,
|
||||||
sessionMaker: () -> S = {
|
sessionMaker: () -> S = {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
Unit as S
|
Unit as S
|
||||||
},
|
},
|
||||||
): KiloClient<S> {
|
): KiloClient<S> {
|
||||||
|
return KiloClient(clientInterface, secretKey) {
|
||||||
|
KiloConnectionData(webSocketTransportDevice(path), sessionMaker())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create kilopaarsec transport over websocket (ws or wss).
|
||||||
|
* @param path websocket path (must start with ws:// or wss:// and contain a path part)
|
||||||
|
* @client use default [HttpClient], it installs [WebSockets] plugin
|
||||||
|
*/
|
||||||
|
fun webSocketTransportDevice(
|
||||||
|
path: String,
|
||||||
|
client: HttpClient = HttpClient { install(WebSockets) },
|
||||||
|
): Transport.Device {
|
||||||
var u = Url(path)
|
var u = Url(path)
|
||||||
if (u.encodedPath.length <= 1)
|
if (u.encodedPath.length <= 1)
|
||||||
u = URLBuilder(u).apply {
|
u = URLBuilder(u).apply {
|
||||||
encodedPath = "/kp"
|
encodedPath = "/kp"
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
return KiloClient(clientInterface, secretKey) {
|
val input = Channel<UByteArray>()
|
||||||
val input = Channel<UByteArray>()
|
val output = Channel<UByteArray>()
|
||||||
val output = Channel<UByteArray>()
|
val closeHandle = CompletableDeferred<Boolean>()
|
||||||
val closeHandle = CompletableDeferred<Boolean>()
|
globalLaunch {
|
||||||
globalLaunch {
|
val log = LogTag("KC:${counter.incrementAndGet()}")
|
||||||
val log = LogTag("KC:${counter.incrementAndGet()}")
|
client.webSocket({
|
||||||
client.webSocket({
|
url.protocol = u.protocol
|
||||||
url.protocol = u.protocol
|
url.host = u.host
|
||||||
url.host = u.host
|
url.port = u.port
|
||||||
url.port = u.port
|
url.encodedPath = u.encodedPath
|
||||||
url.encodedPath = u.encodedPath
|
url.parameters.appendAll(u.parameters)
|
||||||
url.parameters.appendAll(u.parameters)
|
log.info { "kiloparsec server URL: $url" }
|
||||||
log.info { "kiloparsec server URL: $url" }
|
}) {
|
||||||
}) {
|
log.info { "connected to the server" }
|
||||||
log.info { "connected to the server" }
|
|
||||||
// println("SENDING!!!")
|
// println("SENDING!!!")
|
||||||
// send("Helluva")
|
// send("Helluva")
|
||||||
launch {
|
launch {
|
||||||
try {
|
try {
|
||||||
for (block in output) {
|
for (block in output) {
|
||||||
send(block.toByteArray())
|
send(block.toByteArray())
|
||||||
|
}
|
||||||
|
log.info { "input is closed, closing the websocket" }
|
||||||
|
if (closeHandle.isActive) closeHandle.complete(true)
|
||||||
|
} catch (_: ClosedSendChannelException) {
|
||||||
|
log.info { "send channel closed" }
|
||||||
|
} catch (_: CancellationException) {
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
log.info { "unexpected exception in websock sender: ${t.stackTraceToString()}" }
|
||||||
|
closeHandle.completeExceptionally(t)
|
||||||
|
}
|
||||||
|
if (closeHandle.isActive) closeHandle.complete(false)
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
for (f in incoming) {
|
||||||
|
if (f is Frame.Binary) {
|
||||||
|
input.send(f.readBytes().toUByteArray())
|
||||||
|
} else {
|
||||||
|
log.warning { "ignoring unexpected frame of type ${f.frameType}" }
|
||||||
}
|
}
|
||||||
log.info { "input is closed, closing the websocket" }
|
|
||||||
if (closeHandle.isActive) closeHandle.complete(true)
|
|
||||||
} catch (_: ClosedSendChannelException) {
|
|
||||||
log.info { "send channel closed" }
|
|
||||||
}
|
|
||||||
catch(_: CancellationException) {}
|
|
||||||
catch(t: Throwable) {
|
|
||||||
log.info { "unexpected exception in websock sender: ${t.stackTraceToString()}" }
|
|
||||||
closeHandle.completeExceptionally(t)
|
|
||||||
}
|
}
|
||||||
|
if (closeHandle.isActive) closeHandle.complete(true)
|
||||||
|
} catch (_: CancellationException) {
|
||||||
|
if (closeHandle.isActive) closeHandle.complete(false)
|
||||||
|
} catch (_: ClosedReceiveChannelException) {
|
||||||
|
log.warning { "receive channel closed unexpectedly" }
|
||||||
|
if (closeHandle.isActive) closeHandle.complete(false)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
log.exception { "unexpected error" to t }
|
||||||
if (closeHandle.isActive) closeHandle.complete(false)
|
if (closeHandle.isActive) closeHandle.complete(false)
|
||||||
}
|
}
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
for (f in incoming) {
|
|
||||||
if (f is Frame.Binary) {
|
|
||||||
input.send(f.readBytes().toUByteArray())
|
|
||||||
} else {
|
|
||||||
log.warning { "ignoring unexpected frame of type ${f.frameType}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (closeHandle.isActive) closeHandle.complete(true)
|
|
||||||
} catch (_: CancellationException) {
|
|
||||||
if (closeHandle.isActive) closeHandle.complete(false)
|
|
||||||
} catch (_: ClosedReceiveChannelException) {
|
|
||||||
log.warning { "receive channel closed unexpectedly" }
|
|
||||||
if (closeHandle.isActive) closeHandle.complete(false)
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
log.exception { "unexpected error" to t }
|
|
||||||
if (closeHandle.isActive) closeHandle.complete(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!closeHandle.await()) {
|
|
||||||
log.warning { "Client is closing with error" }
|
|
||||||
throw RemoteInterface.ClosedException()
|
|
||||||
}
|
|
||||||
output.close()
|
|
||||||
input.close()
|
|
||||||
}
|
}
|
||||||
log.info { "closing connection" }
|
if (!closeHandle.await()) {
|
||||||
|
log.warning { "Client is closing with error" }
|
||||||
|
throw RemoteInterface.ClosedException()
|
||||||
|
}
|
||||||
|
output.close()
|
||||||
|
input.close()
|
||||||
}
|
}
|
||||||
val device = ProxyDevice(input, output) {
|
log.info { "closing connection" }
|
||||||
// we need to explicitly close the coroutine job, or it can hang for a long time
|
|
||||||
// leaking resources.
|
|
||||||
closeHandle.complete(true)
|
|
||||||
// job.cancel()
|
|
||||||
}
|
|
||||||
KiloConnectionData(device, sessionMaker())
|
|
||||||
}
|
}
|
||||||
|
val device = ProxyDevice(input, output) {
|
||||||
|
// we need to explicitly close the coroutine job, or it can hang for a long time
|
||||||
|
// leaking resources.
|
||||||
|
closeHandle.complete(true)
|
||||||
|
// job.cancel()
|
||||||
|
}
|
||||||
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user