Now I'm developing server application with ktor 2(2.0.0-eap-256).
What I want to do is, according to header or other information, Reject or set adequate http status to response and do not let request go into service logic.
Below is What I tried.
val testPlugin = createApplication("testPlugin") {
onCall {
if (call.request.headers["auth"] == null) {
call.respond(HttpStatusCode.BadRequest)
return#onCall
}
}
}
fun Application.testRouting() {
routing {
get("/") { call.respond("hello") }
}
}
fun Application.applyPlugin() {
install(testPlugin)
}
But request goes into service logic defined by routing(with response which has HttpStatusCode.BadRequest). Is there any idea?
And also, I want to ask my understand about onCall/onCallReceive/onCallRespond is right
onCall is invoked first, when request come.
then, onCallReceive is invoked to handle request data such as file, body, etc
after all service logic, onCallRespond is invoked.
Edit
About the last question, it is solved. onCallReceive is called when I invoke call.receive() to get request content
Edit
Add routing code
Edit
So, I edit plugin like this.
val testPlugin = createApplication(
name = "testPlugin",
createConfiguration = { TestPluginConfig() }
) {
pluginConfig.apply {
pipeline!!.intercept(ApplicationCallPipeline.Plugins){
if (call.request.headers["auth"] == null) {
call.respond(HttpStatusCode.BadRequest)
finish()
}
}
}
}
data class TestPluginConfig(
var pipeline: Application? = null // io.ktor.sever.Application
)
fun Application.testRouting() {
routing {
get("/") { call.respond("hello") }
}
}
fun Application.applyPlugin() {
val pipeline = this // io.ktor.sever.Application
install(testPlugin) { pipeline = pipeline }
}
It works just as I wanted
very thanks to Aleksei Tirman
According to Rustam Siniukov on the kotlin slack here it's enough to use call.respond in the plugin.
My tests confirmed this.
Related
emphasized textI am trying to use Kotlin Flow to process some data asynchronously and in parallel, and stream the responses to the client as they occur, as opposed to waiting until all the jobs are complete.
After unsuccessfully trying to just send the flow itself to the response, like this: call.respond(HttpStatusCode.OK, flow.toList())
... I tinkered for hours trying to figure it out, and came up with the following. Is this correct? It seems there should be a more idiomatic way of sending a Flow<MyData> as a response, like one can with a Flux<MyData> in Spring Boot.
Also, it seems that using the below method does not cancel the Flow when the HTTP request is cancelled, so how would one cancel it in Ktor?
data class MyData(val number: Int)
class MyService {
fun updateAllJobs(): Flow<MyData> =
flow {
buildList { repeat(10) { add(MyData(Random.nextInt())) } }
// Docs recommend using `onEach` to "delay" elements.
// However, if I delay here instead of in `map`, all elements are held
// and emitted at once at the very end of the cumulative delay.
// .onEach { delay(500) }
.map {
// I want to emit elements in a "stream" as each is computed.
delay(500)
emit(it)
}
}
}
fun Route.jobRouter() {
val service: MyService by inject() // injected with Koin
put("/jobs") {
val flow = service.updateAllJobs()
// Just using the default Jackson mapper for this example.
val mapper = jsonMapper { }
// `respondOutputStream` seems to be the only way to send a Flow as a stream.
call.respondOutputStream(ContentType.Application.Json, HttpStatusCode.OK) {
flow.collect {
println(it)
// The data does not stream without the newline and `flush()` call.
write((mapper.writeValueAsString(it) + "\n").toByteArray())
flush()
}
}
}
}
The best solution I was able to find (although I don't like it) is to use respondBytesWriter to write data to a response body channel. In the handler, a new job to collect the flow is launched to be able to cancel it if the channel is closed for writing (HTTP request is canceled):
fun Route.jobRouter(service: MyService) {
put("/jobs") {
val flow = service.updateAllJobs()
val mapper = jsonMapper {}
call.respondBytesWriter(contentType = ContentType.Application.Json) {
val job = launch {
flow.collect {
println(it)
try {
writeStringUtf8(mapper.writeValueAsString(it))
flush()
} catch (_: ChannelWriteException) {
cancel()
}
}
}
job.join()
}
}
}
I am a complete beginner in terms of Kotlin and I am finding some issues while trying to test out a Ktor based application.
I have a file in my endpoints package localized at org.example.endpoints.hello
this file contains a fun Application.hello that implements an endpoint for my application.
This endpoint acts as a wrapper for another API, so inside that same file I have a
fun callOtherAPI(): ResponseContainer {
// networking stuff
return ResponseContainer(message: "some stuff")
}
This function gets called inside the Application's function routing implementation as such:
routing {
get("/hello") {
call.respond(callOtherAPI())
}
}
Now to the issue:
My test currently looks like this:
#Test
fun testHello() = testApplication {
application {
hello()
}
mockkStatic(::callOtherAPI)
every { callOtherAPI() } returns ResponseContainer("hello")
print(callOtherAPI()) // This actually returns the mocked response, which is what I want
client.get("/hello").apply {
val expected = ResponseContainer("hello")
val response = jacksonObjectMapper().readValue<ResponseContainer>(bodyAsText())
assertEquals(HttpStatusCode.OK, status)
assertEquals(expected.message, response.message) // This assert fails because the internal call to callOtherAPI() is not being mocked.
}
}
So the problem that I am facing is that while the mocked function is being mocked within the context of the test, it is not being mocked when called internally by the routing implementation.
Can someone point me to good documentation to figure this out, I've been at it for the past two hours to no avail :/
Thanks!
You can declare a parameter for the callOtherAPI function in the hello method. For the production and testing environment you will pass different functions in this case. Here is your code rewritten:
#Test
fun testHello() = testApplication {
application {
// hello(::callOtherAPI) this call will be for the production environment
hello { ResponseContainer("hello") }
}
client.get("/hello").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals("{\"message\":\"hello\"}", bodyAsText())
}
}
data class ResponseContainer(val message: String)
fun Application.hello(callOtherAPI: () -> ResponseContainer) {
install(ContentNegotiation) {
jackson()
}
routing {
get("/hello") {
call.respond(callOtherAPI())
}
}
}
fun callOtherAPI(): ResponseContainer {
// networking stuff
return ResponseContainer("some stuff")
}
I build a web server with Ktor, and want to cache API method result. But I don't know how to get the response body from call.response. Code like below
fun Application.module(){
// before method was called
intercept(ApplicationCallPipeline.Features) {
val cache = redis.get("cache")
if(cache) {
call.respond(cache)
}
return #intercept finish()
}
// after method was called
intercept(ApplicationCallPipeline.Fallback) {
// TODO, how to get call.response.body
redis.set("cache", call.response.body)
}
}
If I can't get the response body, Any other solution to cache the result of an API method in Ktor?
Inside an interceptor, for the ApplicationCallPipeline.Features phase you can add a phase just before the ApplicationSendPipeline.Engine and intercept it to retreive a response body (content):
val phase = PipelinePhase("phase")
call.response.pipeline.insertPhaseBefore(ApplicationSendPipeline.Engine, phase)
call.response.pipeline.intercept(phase) { response ->
val content: ByteReadChannel = when (response) {
is OutgoingContent.ByteArrayContent -> ByteReadChannel(response.bytes())
is OutgoingContent.NoContent -> ByteReadChannel.Empty
is OutgoingContent.ReadChannelContent -> response.readFrom()
is OutgoingContent.WriteChannelContent -> GlobalScope.writer(coroutineContext, autoFlush = true) {
response.writeTo(channel)
}.channel
else -> error("")
}
// Do something with content
}
I have a spring boot kotlin app that creates a web socket connection to another spring app, sends multiple "subscribe" messages, and then needs to wait for receipt of one response per subscription on the web socket connection. The number of subscriptions open at a given time could be up to a few thousand.
I've come up with a basic working solution using CompletableFuture and coroutines, as below. Is there a more idiomatic or concise way to do this task, or is this a fine solution? Any suggestions for improvement are appreciated.
// InputObject / ResponseObject are generic placeholders
fun getItems(inputObjects: List<InputObject>): List<ResponseObject> {
val ret: ConcurrentLinkedQueue<ResponseObject> = ConcurrentLinkedQueue()
// create a completable future for each input object
val subscriptions: MutableMap<String, CompletableFuture<ResponseObject>> = mutableMapOf()
inputObjects.forEach {
subscriptions[it.id] = CompletableFuture()
}
// create web socket client configured with a lambda handler to
// fulfill each subscription
// each responseObject.id matches one inputObject.id
val client = createWebSocketClient({
try {
val responseObject = objectMapper.readValue(it, ResponseObject::class.java)
subscriptions[responseObject.id]?.complete(responseObject)
} catch (e: Exception) {
logger.warn("Exception reading data: ${e.message}")
}
})
runBlocking {
coroutineScope {
for (item in inputObjects) {
launch {
// create and send a subscribe request
client.sendMessage(createSubscribe(item.id))
// wait for each future to complete
// uses CompletableFuture extension await() from kotlinx-coroutines-jdk8
val result = subscriptions[item.id]?.await()
if (result != null) {
ret.add(result)
}
}
}
}
}
client.close()
return ret.toList()
}
edit: I found a similar question: How to pass result as it comes using coroutines?
Which options makes the most sense?
fun getItems(inputObjects: List<InputObject>): List<ResponseObject> {
val subscriptions = ids.associateTo(mutableMapOf()) { it.id to CompletableFuture<ResponseObject>() }
val client = createWebSocketClient({
try {
val responseObject = objectMapper.readValue(it, ResponseObject::class.java)
subscriptions[responseObject.id]?.complete(responseObject)
} catch (e: Exception) {
logger.warn("Exception reading data: ${e.message}")
}
})
return runBlocking(Dispatchers.IO) {
inputObjects
.mapNotNull {
client.sendMessage(createSubscribe(item.id))
subscriptions[item.id]?.await()
}
}
}
I made a kotlin-ktor application, what i wanted to achieve is that it is modular that anytime any pipelines inside the application maybe removed from the source code without breaking any functionalities. So i decided i want to move a websocket implementation to separate class
but i faced an issue where the coroutine inside the lambda expression terminates immediately.
link-github issue
Can someone enlighten me about the coroutine setup on this, and how I can still keep this as modular without this kind of issue
working ktor websocket
fun Application.socketModule() = runBlocking {
// other declarations
......
routing {
val sessionService = SocketSessionService()
webSocket("/home") {
val chatSession = call.sessions.get<ChatSession>()
println("request session: $chatSession")
if (chatSession == null) {
close(CloseReason(CloseReason.Codes.VIOLATED_POLICY, "empty Session"))
return#webSocket
}
send(Frame.Text("connected to server"))
sessionService.addLiveSocket(chatSession.id, this)
sessionService.checkLiveSocket()
}
thread(start = true, name = "socket-monitor") {
launch {
sessionService.checkLiveSocket()
}
}
}
}
kotlin-ktor auto-close web socket
code below closes the socket automatically
Socket Module
class WebSocketServer {
fun createWebSocket(root: String, routing: Routing) {
println("creating web socket server")
routing.installSocketRoute(root)
}
private fun Routing.installSocketRoute(root: String) {
val base = "/message/so"
val socketsWeb = SocketSessionService()
webSocket("$root$base/{type}") {
call.parameters["type"] ?: throw Exception("missing type")
val session = call.sessions.get<ChatSession>()
if (session == null) {
println( "WEB-SOCKET:: client session is null" )
close(CloseReason(CloseReason.Codes.VIOLATED_POLICY, "No Session"))
return#webSocket
}
socketsWeb.addLiveSocket(session.id, this)
thread(start= true, name = "thread-live-socket") {
launch {
socketsWeb.checkLiveSocket()
}
}
}
}
}
Application Module
fun Application.socketModule() = runBlocking {
// other delcarations
.....
install(Sessions) {
cookie<ChatSession>("SESSION")
}
intercept(ApplicationCallPipeline.Features) {
if (call.sessions.get<ChatSession>() == null) {
val sessionID = generateNonce()
println("generated Session: $sessionID")
call.sessions.set(ChatSession(sessionID))
}
}
routing {
webSocketServer.createWebSocket("/home", this)
}
}
I quite dont understand why the coroutine insdie webSocket lamda is completed.
Can someone show me other/right approach on this one.