CompletableFuture<T> freezes UI - kotlin

I am currently working on an api for my client application which needs to process http requests (using unirest) asynchronously as of now. I am new to CompletableFuture and haven't worked with anything similar up to this point. I was wondering whether the following structure makes sense:
// Request.kt (simplified)
class Request<T>(
// other variables relevant to the request such as body or path ...
private val responseType: Class<T>
) {
fun prepareRequest(action: (HttpRequest<*>) -> U): U {
// preprocesses the request, adds body if necessary and returns the request itself
}
fun executeAsync(action: (HttpResponse<T>) -> Unit) {
prepareRequest { req ->
action(req.asObjectAsync(responseType).get()) // Unirest call that (still) freezes the UI
}
}
// Builder logic ...
}
// ApiClient.kt (simplified)
abstract class ApiClient {
protected fun <T> executeAsync(req: Request<T>, action: (T) -> Unit) {
req.executeAsync { res ->
if (res.isSuccess){
action(res.body)
} else {
throw RuntimeException("res != 200")
}
}
}
}
// AuthClient.kt (simplified)
class AuthClient : ApiClient() {
fun signin(email: String, password: String, onSuccess: () -> Unit) {
executeAsync(
Request.builder(TokenModel::class.java)
.post("/signin")
.body(SignInModel(email, password))
.build()
) {
onSuccess() // this is going to refresh the UI, once the http request has been executed
}
}
}
As the call to get on CompletableFuture freezes the UI I thought of including an Executor or a Thread instead so that executeAsync in Request becomes the following:
fun executeAsync(action: (HttpResponse<T>) -> Unit) {
prepareRequest { req ->
Executors.newSingleThreadScheduledExecutor().execute {
action(it.asObjectAsync(responseType).get())
}
}
}
Is my structure overly complex or does it have to be like that? Do I need the Thread/Executor or can this be achieved in a different way?

Related

Ktor Resources validation plugin

I've a Ktor server application using the Resources plugin for type-safe routing. Now I want to create a custom plugin to validate the resource instance.
But I can't figure what is the correct phase for intercepting the pipeline.
A custom "Validation Phase" inserted before the call phase seems to be executed to early as the Resource instance is not yet added to the ApplicationCall attributes. I do not really understand why thats the case, because the decoding of the Resource instance should by done in the Plugins phase? Found the following in io.ktor.server.resources.Routing:
public fun <T : Any> Route.handle(
serializer: KSerializer<T>,
body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit
) {
intercept(ApplicationCallPipeline.Plugins) {
val resources = application.plugin(Resources)
try {
val resource = resources.resourcesFormat.decodeFromParameters(serializer, call.parameters)
call.attributes.put(ResourceInstanceKey, resource)
} catch (cause: Throwable) {
throw BadRequestException("Can't transform call to resource", cause)
}
}
...
}
If I add my custom validation phase after the call phase it's executed to late, after the route handler.
Here some example code...
Route and Resource:
fun Route.exampleRouting() {
get<ExampleResource> { example ->
println("Validated value: ${example.somevalue}")
call.respond(HttpStatusCode.OK)
}
}
fun Application.registerExampleRoutes() {
routing {
exampleRouting()
}
}
#Serializable
#Resource("/example")
class ExampleResource(val somevalue: String)
Custom validation plugin:
val ResourcesValidation = createApplicationPlugin("ResourcesValidation") {
on(ValidationHook) { call ->
val resourceInstanceKey =
call.attributes.allKeys.filterIsInstance<AttributeKey<Any>>().find { it.name == "ResourceInstance" }
// PROBLEM: resourceInstanceKey is null here, ResourceInstance not yet added to call attributes
resourceInstanceKey?.let {
val resourceInstance = call.attributes[resourceInstanceKey]
// Validate resource instance here...
println("validated")
}
}
}
object ValidationHook : Hook<suspend (ApplicationCall) -> Unit> {
val ValidationPhase: PipelinePhase = PipelinePhase("Validation")
override fun install(
pipeline: ApplicationCallPipeline,
handler: suspend (ApplicationCall) -> Unit
) {
pipeline.insertPhaseBefore(ApplicationCallPipeline.Call, ValidationPhase)
pipeline.intercept(ValidationPhase) { handler(call) }
}
}
And of cause installing the plugin and registering the routes in the Application:
fun Application.module() {
...
install(ResourcesValidation)
...
registerExampleRoutes()
...
}
I've tried the same with the Base API but same result.
So..is there any way to intercept the pipeline at the right time to validate the Resource instance before the route handler is executed?
To solve your problem you can write a RouteScopedPlugin and install it into the routing because a resource instance is put into the call attributes while interception of a route's call pipeline, not an application's pipeline.
fun main() {
embeddedServer(Netty, port = 4444) {
install(Resources)
routing {
install(ResourcesValidation)
get<ExampleResource> { example ->
println("Validated value: ${example.somevalue}")
call.respond(HttpStatusCode.OK)
}
}
}.start(wait = true)
}
val ResourcesValidation = createRouteScopedPlugin("ResourcesValidation") {
on(ValidationHook) { call ->
try {
val resourceInstance = call.attributes[AttributeKey("ResourceInstance")]
println("validated")
} catch (_: IllegalStateException) {
// attribute not found
}
}
}
object ValidationHook : Hook<suspend (ApplicationCall) -> Unit> {
val ValidationPhase: PipelinePhase = PipelinePhase("Validation")
override fun install(
pipeline: ApplicationCallPipeline,
handler: suspend (ApplicationCall) -> Unit
) {
pipeline.insertPhaseAfter(ApplicationCallPipeline.Plugins, ValidationPhase)
pipeline.intercept(ValidationPhase) { handler(call) }
}
}

Kotlin coroutines, how to async alist of calls and return the result as a map

var responseMap = mutableMapOf<VendorType, ChargeResponse>()
requests.forEach {
val response = when (it.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> {
throw NotImplementedError("${it.vendorType} does not support yet")
}
}
responseMap[it.vendorType] = response
}
responseMap
So I want all the service.charge function run in separate thread. Return the map when all is done
Hope to solve your problem:
Assume your service and request like this:
interface Service {
suspend fun chargeForType1(): ChargeResponse
suspend fun chargeForType2(): ChargeResponse
}
data class Request(val vendorType: VendorType)
suspend fun requestAll(requests: List<Request>): Map<VendorType, ChargeResponse> {
return coroutineScope {
requests
.map { request ->
async {
request.vendorType to when (request.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> throw NotImplementedError("${request.vendorType} does not support yet")
}
}
}
.awaitAll()
.toMap()
}
}

Pass lambda as extension method

I'm currently learning kotlin and have came into the following scenario. In Ktor server there's a method with the following signature:
fun Route.webSocket(protocol: String? = null, handler: suspend DefaultWebSocketServerSession.() -> Unit) {
webSocketRaw(protocol) {
proceedWebSocket(handler)
}
}
where I'm supposed to interact with it somehow like this:
embeddedServer(Netty, 8080) {
install(Routing) {
webSocket("/ws") {
// Handle websocket connection here
}
}
}
Meaning websocket accepts labda that's an extension method of DefaultWebSocketServerSession and has it's context. I would like to convert this lambda into a handler so I can pass it from somewhere else, I imagine it should look something like that:
embeddedServer(Netty, 8080) {
install(Routing) {
webSocket("/ws", myHandler::handle)
}
}
//...
fun suspend handle(context: DefaultWebSocketServerSession): Unit {
// Handle websocket connection here
}
So, my question is how do I convert suspend DefaultWebSocketServerSession.() -> Unit to(DefaultWebSocketServerSession) -> Unit, or how do I Implement a handler with suspend DefaultWebSocketServerSession.() -> Unit signature so I can pass it from the outside?
PS
I know I could do this
embeddedServer(Netty, 8080) {
install(Routing) {
webSocket("/ws") {
myHandler.handle(this)
}
}
}
But that doesn't feel elegant
There is no need to convert anything yourself. Kotlin converts between Method References, Function Literals and Function Literals with Receiver. Look at this example:
class A
class AHandler {
fun handle(a: A) {
println("AHandler $a")
}
}
fun useLambdaWithReceiver(lambda: A.()->Unit) {
val a = A()
lambda(a)
}
fun useNormalLambda(lambda: (A)->Unit) {
useLambdaWithReceiver(lambda)
}
fun main() {
val handler = AHandler()
useLambdaWithReceiver {
println("useLambdaWithReceiver $this")
}
useNormalLambda {
println("useNormalLambda $it")
}
useLambdaWithReceiver(handler::handle)
useNormalLambda(handler::handle)
}
Output:
useLambdaWithReceiver A#174d20a
useNormalLambda A#3c756e4d
AHandler A#2ef5e5e3
AHandler A#6d00a15d
Everything compiled and converted automatically.
So, you can just pass your handler method and it should be fine, unless there is a bug with suspend modifier I am not aware of.

Akka-Http: how to timeout a HttpResponse strict entity in a test

Here is my code
import akka.http.javadsl.Http
// some initialization omitted
inline fun <reified T> executeRequest(request: HttpRequest, crossinline onError: (HttpResponse) -> Unit): CompletionStage<T?> {
val unmarshaller = GsonMarshaller.unmarshaller(T::class.java)
return http.singleRequest(request).thenCompose { httpResponse: HttpResponse ->
if (httpResponse.status() == StatusCodes.OK || httpResponse.status() == StatusCodes.CREATED) {
unmarshaller.unmarshal(httpResponse.entity().withContentType(ContentTypes.APPLICATION_JSON), dispatcher, materializer)
} else {
onError(httpResponse) // invoke lambda to notify of error
httpResponse.discardEntityBytes(materializer)
CompletableFuture.completedFuture(null as T?)
}
}
}
class TradingActor(
val materializer: ActorMaterializer,
val dispatcher: ExecutionContextExecutor
): AbstractLoggingActor() {
fun submitNewOrder(request: Request, onFailed: (text: String) -> Unit) {
executeRequest<OrderAnswer>(request) {
it.entity().toStrict(5_000, materializer).thenApply { entity ->
onFailed("API Call Failed")
}
}.thenAccept {
println("OK")
}
}
}
I have to write a test checking that if .entity().toStrict(5_000, materializer) timeout expires then onFailed("API Call Failed") is called. The current code do not call onFailed("") in case of timeout, therefore I want this test.
my test contains
val response = akka.http.javadsl.model.HttpResponse.create()
.withStatus(StatusCodes.OK)
.withEntity("""{'s': 'text'}""")
Mockito.`when`(http.singleRequest(any()))
.then {
CompletableFuture.completedFuture<akka.http.javadsl.model.HttpResponse>(response)
}
but I don;t know how to make toStrict() expire.
As I understand from your question you can create mock object for ResponseEntity and create own implementation for toStrict() method that will have a delay. Something like in example below from here -> Can I delay a stubbed method response with Mockito?.
when(mock.load("a")).thenAnswer(new Answer<String>() {
#Override
public String answer(InvocationOnMock invocation){
Thread.sleep(5000);
return "ABCD1234";
}
});
Than you can set it in your response object.
val response = akka.http.javadsl.model.HttpResponse.create()
.withStatus(StatusCodes.OK)
.withEntity(mockedEntity)

How to write rx concatArrayEager equivalent in Kotlin CoRoutine?

I would like to convert my rxJava Code to Kotlin CoRoutine.
Below is the code makes both the api and db call and returns the data to UI whatever comes first. Let us say if DB response happens to be quicker than the api. In that case still, the api response would continue until it receives the data to sync with db though it could have done the UI update earlier.
How Would I do it?
class MoviesRepository #Inject constructor(val apiInterface: ApiInterface,
val MoviesDao: MoviesDao) {
fun getMovies(): Observable<List<Movie>> {
val observableFromApi = getMoviesFromApi()
val observableFromDb = getMoviesFromDb()
return Observable.concatArrayEager(observableFromApi, observableFromDb)
}
fun getMoviesFromApi(): Observable<List<Movie>> {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDb(): Observable<List<Movie>> {
return MoviesDao.queryMovies()
.toObservable()
.doOnNext {
//Print log it.size :)
}
}
}
As the first step you should create suspend funs for your ApiInterface and MovieDao calls. If they have some callback-based API, you can follow these official instructions.
You should now have
suspend fun ApiInterface.suspendGetMovies(): List<Movie>
and
suspend fun MoviesDao.suspendQueryMovies(): List<Movie>
Now you can write this code:
launch(UI) {
val fromNetwork = async(UI) { apiInterface.suspendGetMovies() }
val fromDb = async(UI) { MoviesDao.suspendQueryMovies() }
select<List<Movie>> {
fromNetwork.onAwait { it }
fromDb.onAwait { it }
}.also { movies ->
// act on the movies
}
}
The highlight is the select call which will simultaneously await on both Deferreds and act upon the one that gets completed first.
If you want to ensure you act upon the result from the network, you'll need some more code, for example:
val action = { movies: List<Movie> ->
// act on the returned movie list
}
var gotNetworkResult = false
select<List<Movie>> {
fromNetwork.onAwait { gotNetworkResult = true; it }
fromDb.onAwait { it }
}.also(action)
if (!gotNetworkResult) {
action(fromNetwork.await())
}
This code will act upon the DB results only if they come in before the network results, which it will process in all cases.
Something along those lines should work:
data class Result(val fromApi: ???, val fromDB: ???)
fun getMovies(): Result {
val apiRes = getMoviesFromApiAsync()
val dbRes = getMoviesFromDbAsync()
return Result(apiRes.await(), dbRes.await())
}
fun getMoviesFromApiAsync() = async {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDbAsync() = async {
return MoviesDao.queryMovies()
}
I don't know what you're returning, so I just put ??? instead.