I am building a receiver for Apache Spark after a template of szelvenskiythat gets an http-URL as input. I can package a jar with sbt, but on runtime the application throws a java.lang.NoClassDefFoundError: com.ning.http.client.AsyncHandler exception. Please find the code to implement the receiver here:
import org.apache.spark.streaming.receiver.Receiver
import org.apache.spark.storage.StorageLevel
import org.apache.spark.Logging
import com.ning.http.client.AsyncHttpClientConfig
import com.ning.http.client.AsyncHandler
import com.ning.http.client.AsyncHttpClient
import com.ning.http.client._
import scala.collection.mutable.ArrayBuffer
import java.io.OutputStream
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.io.BufferedReader
import java.io.InputStream
import java.io.PipedInputStream
import java.io.PipedOutputStream
class ParkingReceiver(url: String) extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) with Logging {
#transient var client: AsyncHttpClient = _
#transient var inputPipe: PipedInputStream = _
#transient var outputPipe: PipedOutputStream = _
def onStart() {
val cf = new AsyncHttpClientConfig.Builder()
cf.setRequestTimeout(Integer.MAX_VALUE)
cf.setReadTimeout(Integer.MAX_VALUE)
cf.setPooledConnectionIdleTimeout(Integer.MAX_VALUE)
client= new AsyncHttpClient(cf.build())
inputPipe = new PipedInputStream(1024 * 1024)
outputPipe = new PipedOutputStream(inputPipe)
val producerThread = new Thread(new DataConsumer(inputPipe))
producerThread.start()
client.prepareGet(url).execute(new AsyncHandler[Unit]() {
def onBodyPartReceived(bodyPart: HttpResponseBodyPart) = {
bodyPart.writeTo(outputPipe)
AsyncHandler.STATE.CONTINUE
}
def onStatusReceived(status: HttpResponseStatus) = {
AsyncHandler.STATE.CONTINUE
}
def onHeadersReceived(headers: HttpResponseHeaders) = {
AsyncHandler.STATE.CONTINUE
}
def onCompleted = {
println("completed")
}
def onThrowable(t: Throwable)={
t.printStackTrace()
}
})
}
def onStop() {
if (Option(client).isDefined) client.close()
if (Option(outputPipe).isDefined) {
outputPipe.flush()
outputPipe.close()
}
if (Option(inputPipe).isDefined) {
inputPipe.close()
}
}
class DataConsumer(inputStream: InputStream) extends Runnable
{
override
def run()
{
val bufferedReader = new BufferedReader( new InputStreamReader( inputStream ))
var input=bufferedReader.readLine()
while(input!=null){
store(input)
input=bufferedReader.readLine()
}
}
}
}
Have any of you experienced this before or may know what is missing in the code?
Related
I get below error while trying to access refferences from my Resources.kt file
.
Below shows my full code on LoginViewModel.kt
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bumptech.glide.load.engine.Resource
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.firestore.auth.User
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
#HiltViewModel
class LoginViewModel #Inject constructor(
private val firebaseAuth: FirebaseAuth
): ViewModel() {
private val _login = MutableSharedFlow<Resource<FirebaseUser>>()
// MutableSharedFlow<Resource<FirebaseUser>>()
val login = _login.asSharedFlow()
fun login(email: String, password: String){
viewModelScope.launch {
_login.emit(Resource.Loading())
}
firebaseAuth.signInWithEmailAndPassword(
email,password
).addOnSuccessListener {
viewModelScope.launch {
it.user?.let {
_login.emit(Resource.Success(it))
}
}
}.addOnFailureListener {
viewModelScope.launch {
_login.emit(Resource.Error(it.message.toString()))
}
}
}
}
Below shows the Resource.kt file
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.util
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
){
class Success<T>(data:T):Resource<T>(data)
class Error<T>(message:String):Resource<T>(message = message)
class Loading <T>: Resource<T>()
class Unspecified <T>: Resource<T>()
}
I have tried to implement using the same way I did for other view models.
I did the same approach to access same variables on RegisterViewModel.kt and it works without any error. Below is the code for that functioning view model
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.viewmodel
import androidx.lifecycle.ViewModel
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.runBlocking
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.data.User
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.util.*
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.util.Constants.USER_COLLECTION
import javax.inject.Inject
#HiltViewModel
class RegisterViewModel #Inject constructor(
private val firebaseAuth : FirebaseAuth,
private val db: FirebaseFirestore
) : ViewModel() {
private val _register = MutableStateFlow<Resource<User>>(Resource.Unspecified())
val register : Flow<Resource<User>> = _register
private val _validation = Channel<RegisterFieldsState>()
val validation =_validation.receiveAsFlow()
fun createAccountWithEmailAndPassword(user: User, password: String) {
if (checkValidation(user, password)){
runBlocking {
_register.emit(Resource.Loading())
}
firebaseAuth.createUserWithEmailAndPassword(user.email, password)
.addOnSuccessListener {
it.user?.let {
saveUserInfo(it.uid, user)
// _register.value = Resource.Success(it)
}
}
.addOnFailureListener {
_register.value = Resource.Error(it.message.toString())
}
} else
{
val registerFieldsState = RegisterFieldsState(
validateEmail(user.email), validatePassword(password)
)
runBlocking {
_validation.send(registerFieldsState)
}
}
}
private fun saveUserInfo(userUid: String, user: User) {
db.collection(USER_COLLECTION)
.document(userUid)
.set(user)
.addOnSuccessListener {
_register.value = Resource.Success(user)
}
.addOnFailureListener {
_register.value = Resource.Error(it.message.toString())
}
}
private fun checkValidation(
user: User,
password: String
) : Boolean {
val emailValidation = validateEmail(user.email)
val passwordValidation = validatePassword(password)
val shouldRegister =
emailValidation is RegisterValidation.Success && passwordValidation is RegisterValidation.Success
return shouldRegister
}
}
Can someone help me with solving above issue please.Why it gives an error when trying to call Resource.Loading() from LoginViewModel.kt?
Please check import class in LoginViewModel, You are importing
import com.bumptech.glide.load.engine.Resource
It should be from this package lk.ac.kln.mit.stu.mobileapplicationdevelopment.util
I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}
Problem description
While system end-to-end tests are invoking methods annotated with #TransactionalEventListener, I'm not able to invoke the same methods in narrower tests annotated with #MicronautTest.
I've tested numerous variants with both injected EntityManager and SessionFactory. #MicronautTest(transactional = false) is also tested. Calling JPA-method inside TestSvcWithTxMethod#someMethod is also tested with same result. I've also tried tests without mocking TestAppEventListener.
The below test/code yields
Verification failed: call 1 of 1:
TestAppEventListener(#1).beforeCommit(any())) was not called.
java.lang.AssertionError: Verification failed: call 1 of 1:
TestAppEventListener(#1).beforeCommit(any())) was not called.
Calls to same mock: 1) TestAppEventListener(#1).hashCode()
Environment: Micronaut 3.7.5, Micronaut Data 3.9.3
Minimal reproducible code
Test is failing as well with transactional = false
import io.kotest.core.spec.style.BehaviorSpec
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.kotest5.MicronautKotest5Extension.getMock
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.DefaultTestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWrapper
#MicronautTest
class AppEventWithBeforeCommitListenerMockTest(
testSvcWrapper: TestSvcWrapper,
testAppEventListener: TestAppEventListener
) : BehaviorSpec({
given("context with app event listener") {
`when`("calling someMethod") {
val mockBeforeCommitTestListener = getMock(testAppEventListener)
every { mockBeforeCommitTestListener.beforeCommit(any()) } answers {}
every { mockBeforeCommitTestListener.afterRollback(any()) } answers {}
testSvcWrapper.someMethod(message = "call #1")
verify { mockBeforeCommitTestListener.beforeCommit(any()) }
}
}
}) {
#MockBean(DefaultTestAppEventListener::class)
fun mockTestAppEventListener(): TestAppEventListener = mockk()
}
TestSvcWrapper
import jakarta.inject.Singleton
#Singleton
class TestSvcWrapper(
private val testSvcWithTxMethod: TestSvcWithTxMethod
) {
fun someMethod(message: String) {
testSvcWithTxMethod.someMethod(message)
}
}
TestSvcWithTxMethod
import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional
#Singleton
open class TestSvcWithTxMethod(
private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
#Transactional(Transactional.TxType.REQUIRES_NEW)
open fun someMethod(message: String) {
eventPublisher.publishEvent(TestEvent(message))
}
}
TestEvent
import io.micronaut.core.annotation.Introspected
#Introspected
data class TestEvent(val message: String)
TestAppEventListener
interface TestAppEventListener {
fun beforeCommit(event: TestEvent)
fun afterRollback(event: TestEvent)
}
DefaultTestAppEventListener
import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger
#Singleton
open class DefaultTestAppEventListener : TestAppEventListener {
val receiveCount = AtomicInteger()
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
override fun beforeCommit(event: TestEvent) {
receiveCount.getAndIncrement()
}
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
override fun afterRollback(event: TestEvent) {
receiveCount.getAndIncrement()
}
}
The answer was found in the micronaut-test repo. Key is to inject SynchronousTransactionManager<Any>, create and then commit/rollback transaction.
I was not able to make mock-test from question pass, most likely because of the annotations, but the following tests are working. I made some modifications to the types in question, hence I added code for the new implementations below.
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.micronaut.transaction.SynchronousTransactionManager
import io.micronaut.transaction.support.DefaultTransactionDefinition
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWithTxMethod
#MicronautTest(transactional = false)
class AppEventWithBeforeCommitListenerTest(
testSvcWithTxMethod: TestSvcWithTxMethod,
testAppEventListener: TestAppEventListener,
transactionManager: SynchronousTransactionManager<Any>
) : BehaviorSpec({
given("context with app event listener") {
`when`("calling someMethod with commit") {
val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
testSvcWithTxMethod.someMethod(message = "call #1")
transactionManager.commit(tx)
then("TestAppEventListener should have received message") {
testAppEventListener.beforeCommitReceiveCount.get() shouldBe 1
}
}
`when`("calling someMethod with rollback") {
val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
testSvcWithTxMethod.someMethod(message = "call #2")
transactionManager.rollback(tx)
then("TestAppEventListener should have received message") {
testAppEventListener.afterRollbackReceiveCount.get() shouldBe 1
}
}
}
})
TestSvcWithTxMethod
import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional
#Singleton
open class TestSvcWithTxMethod(
private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
#Transactional
open fun someMethod(message: String) {
eventPublisher.publishEvent(TestEvent(message))
}
}
TestAppEventListener
import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger
#Singleton
open class TestAppEventListener {
val beforeCommitReceiveCount = AtomicInteger()
val afterRollbackReceiveCount = AtomicInteger()
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
open fun beforeCommit(event: TestEvent) {
beforeCommitReceiveCount.getAndIncrement()
}
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
open fun afterRollback(event: TestEvent) {
afterRollbackReceiveCount.getAndIncrement()
}
}
TestEvent (unchanged)
import io.micronaut.core.annotation.Introspected
#Introspected
data class TestEvent(val message: String)
When I run test, I am getting the error when I try to test if Datastore.edit had been called:
java.io.IOException: Unable to create parent directories of /data/user/0/com.example.app.test/files/datastore/user.preferences_pb
It is a small class that handles Datastore, which is the following:
Pref.kt
class Pref {
companion object {
#Volatile
private var INSTANCE: Pref? = null
private var dataStore: DataStore<Preferences>? = null
fun getInstance(context: Context): Pref? {
if (INSTANCE == null) {
synchronized(Pref::class) {
INSTANCE = Pref()
dataStore = context.createDataStore("user")
}
}
return INSTANCE
}
}
suspend fun save(key: String, value: String) {
val dataStoreKey = preferencesKey<String>(key)
dataStore?.edit { settings ->
settings[dataStoreKey] = value
}
}
}
PrefTest.kt
import android.content.Context
import android.provider.ContactsContract
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.datastore.core.DataStore
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.*
import org.junit.Rule
import org.junit.runner.RunWith
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.preferencesKey
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.*
import io.mockk.MockKAnnotations.init
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
#RunWith(AndroidJUnit4::class)
class PrefTest {
private val context : Context = InstrumentationRegistry.getInstrumentation().context
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
private val mockDataStore = mockk<DataStore<Preferences>>()
#Before
fun setup() {
init(this, relaxed = true)
}
#Test
fun saveValue() = runBlocking {
val dataStoreKey = preferencesKey<String>(storeKey)
Pref.getInstance(context)?.save("key", "value")
coVerify { mockDataStore.edit {
dataStoreKey
} }
}
}
in my device, I can see the directory /data/user/0/com.example.app.test/files. But not sure why the datastore directories couldn't be created for it to continue testing.
I'm trying to run local server with KTOR and to cover it with tests. I wrote some code and after writing some tests, the tests successfully raised the local server and passed. However, if I try to start a local server, I get this error
Exception in thread "main" java.lang.AbstractMethodError: Receiver
class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does
not define or inherit an implementation of the resolved method
'abstract boolean getDevelopmentMode()' of interface
io.ktor.application.ApplicationEnvironment.
I attach the code and the stack trace from below
server.kt
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
gson()
}
routing {
val controller = Controller()
get("/converter{from}{to}") {
try {
val fromCurrency = call.request.queryParameters["from"]
val toCurrency = call.request.queryParameters["to"]
val rate = controller.converter(fromCurrency, toCurrency)
val response = Response(
"1 $fromCurrency = $rate $toCurrency",
null
)
call.respond(HttpStatusCode.OK, response)
} catch (e: ControllerException) {
val response = Response(
null,
e.message
)
call.respond(HttpStatusCode.BadRequest, response)
}
}
get {
call.respond(HttpStatusCode.NotFound, Response())
}
}
}
#Serializable
data class Response(
#SerialName("converterResponse")
val converterResponse: String? = null,
#SerialName("errorMessage")
val errorMessage: String? = null
)
converterTest.kt
import com.google.gson.Gson
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ConverterTest {
#Test
fun `Identical Correct Currencies`() {
withTestApplication(Application::module) {
handleRequest(HttpMethod.Get, "/converter?from=USD&to=USD").apply {
assertEquals(HttpStatusCode.OK, response.status())
val expectedResponse = Response("1 USD = 1 USD")
assertEquals(
Gson().toJson(expectedResponse),
response.content
)
}
}
}
}
stacktrace
Exception in thread "main" java.lang.AbstractMethodError: Receiver class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()' of interface io.ktor.application.ApplicationEnvironment.
at io.ktor.application.Application.<init>(Application.kt:20)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:269)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:125)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:245)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:126)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:26)
at ServerKt.main(server.kt:11)
build.gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30" // or kotlin("multiplatform") or any other kotlin plugin
application
}
group = "me.admin"
version = "1.0-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
dependencies {
val kotlin_version = "1.5.30"
val ktor_version = "1.6.3"
implementation("io.ktor:ktor-server-netty:1.4.0")
implementation("io.ktor:ktor-client-cio:1.4.0")
implementation("io.ktor:ktor-html-builder:1.4.0")
implementation("io.ktor:ktor-client-serialization:1.3.2-1.4-M2")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.slf4j:slf4j-api:2.0.0-alpha5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC")
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("io.ktor:ktor-gson:$ktor_version")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClassName = "ServerKt"
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
The dependency io.ktor:ktor-server-netty:1.4.0 causes the AbstractMethodError. To fix it just use the same version for all Ktor dependencies:
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("io.ktor:ktor-html-builder:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")