How to create an HttpResponse object with dummy values in ktor Kotlin? - kotlin

I am using ktor for developing a microservice in Kotlin. For testing a method, I need to create a dummy HttpResponse (io.ktor.client.statement.HttpResponse to be specific) object with status = 200 and body = some json data.
Any idea how I can create it?

You can use mockk or a similar kind of library to mock an HttpResponse. Unfortunately, this is complicated because HttpRequest, HttpResponse, and HttpClient objects are tightly coupled with the HttpClientCall. Here is an example of how you can do that:
val call = mockk<HttpClientCall> {
every { client } returns mockk {}
coEvery { receive(io.ktor.util.reflect.typeInfo<String>()) } returns "body"
every { coroutineContext } returns EmptyCoroutineContext
every { attributes } returns Attributes()
every { request } returns object : HttpRequest {
override val call: HttpClientCall = this#mockk
override val attributes: Attributes = Attributes()
override val content: OutgoingContent = object : OutgoingContent.NoContent() {}
override val headers: Headers = Headers.Empty
override val method: HttpMethod = HttpMethod.Get
override val url: Url = Url("/")
}
every { response } returns object : HttpResponse() {
override val call: HttpClientCall = this#mockk
override val content: ByteReadChannel = ByteReadChannel("body")
override val coroutineContext: CoroutineContext = EmptyCoroutineContext
override val headers: Headers = Headers.Empty
override val requestTime: GMTDate = GMTDate.START
override val responseTime: GMTDate = GMTDate.START
override val status: HttpStatusCode = HttpStatusCode.OK
override val version: HttpProtocolVersion = HttpProtocolVersion.HTTP_1_1
}
}
val response = call.response

I did this with following. I only needed to pass a status code and description, so I didn't bother about other fields.
class CustomHttpResponse(
private val statusCode: Int,
private val description: String
) :
HttpResponse() {
#InternalAPI
override val content: ByteReadChannel
get() = ByteReadChannel("")
override val call: HttpClientCall
get() = HttpClientCall(HttpClient())
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
override val headers: Headers
get() = Headers.Empty
override val requestTime: GMTDate
get() = GMTDate()
override val responseTime: GMTDate
get() = GMTDate()
override val status: HttpStatusCode
get() = HttpStatusCode(statusCode, description)
override val version: HttpProtocolVersion
get() = HttpProtocolVersion(name = "HTTP", major = 1, minor = 1)}

With Ktor 2, it's best to use externalServices block instead of attempting to mock HttpResponse. That way you don't need to attempt and mock the internals of Ktor, and it's not complicated at all.
externalServices {
hosts("https://your-fake-host") {
routing {
get("/api/v1/something/{id}/") {
call.respondText(
"{}",
contentType = ContentType.Application.Json,
status = HttpStatusCode.OK
)
}
}
}
}
This need to be wrapped with testApplication

Related

Custom multipart-form-data serializable in Kotlin/Ktor

Does anyone know how to program the override function convertForReceive of a custom Multipart.FormData converter?
I want to convert the multipart request to my class with the converter but I don't know how it works.
I have:
Application.kt
install(ContentNegotiation) {
json()
register(ContentType.MultiPart.FormData, CustomMultipartConverter)
}
CustomMultipartConverter
object CustomMultipartConverter: ContentConverter {
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
TODO("Not yet implemented")
}
override suspend fun convertForSend(
context: PipelineContext<Any, ApplicationCall>,
contentType: ContentType,
value: Any
): Any? {
TODO("Not yet implemented")
}
}
REQUEST CLASS
class CreatePostRequest(
val text: String,
val image: File? = null
)
ROUTE
route("v1/posts") {
authenticate {
route("create") {
val authJWT = call.authentication.principal as JWTAtuh
val request = call.receive<CreatePostRequest>()
//myCode
call.respond(HttpStatusCode.OK)
}
}
}
You can take SerializationConverter as a reference:
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
val request = context.subject
val channel = request.value as? ByteReadChannel ?: return null
val charset = context.call.request.contentCharset() ?: defaultCharset
val serializer = format.serializersModule.serializer(request.typeInfo)
val contentPacket = channel.readRemaining()
return when (format) {
is StringFormat -> format.decodeFromString(serializer, contentPacket.readText(charset))
is BinaryFormat -> format.decodeFromByteArray(serializer, contentPacket.readBytes())
else -> {
contentPacket.discard()
error("Unsupported format $format")
}
}
}

Not able to return a desired value inside a overidden method using coroutines in kotlin

I am new to kotlin and coroutines.I have been working on a client-server part of an android app.I am using mediasoup-client-android library for this.
I am trying to intialize the sendtransports using createSendTransport() method.This method does have a sendTransportListener which has abstract methods.One of them being is onProduce() which returns a producerId which is a String. awaitEmit() is an asynchronous action and I need this in onProduce().To use awaitEmit() I used coroutines.But I need String as return type instead of Deferred.Is there any other way to implement the mentioned logic? Below is my code
class RoomClient {
suspend fun initTransports(device: Device) {
coroutineScope {
val id: String?
val iceParameters: String?
val iceCandidates: String?
val dtlsParameters: String?
val sctpParameters: String?
try {
val params = JSONObject()
params.put("forceTcp",false)
params.put("rtpCapabilities", this#RoomClient.device?.rtpCapabilities)
val res = socket?.awaitEmit("createWebRtcTransport",params)
val data = res?.get(0) as JSONObject
if (data.has("error")) {
Log.d(TAG, data.getString("error"))
return#coroutineScope
}
id = data.optString("id")
iceParameters = data.optString("iceParameters")
iceCandidates = data.optString("iceCandidates")
dtlsParameters = data.optString("dtlsParameters")
sctpParameters = data.optString("sctpParameters")
} catch (e: Throwable) {
Log.e(TAG, "${e.message}")
return#coroutineScope
}
val sendTransportListener: SendTransport.Listener = object : SendTransport.Listener {
private val listenerTAG = TAG.toString() + "_ProducerTrans"
override fun onProduce(
transport: Transport,
kind: String?,
rtpParameters: String?,
appData: String?
): String? {
this#coroutineScope.async{
var producerId: String? = null
Log.d(listenerTAG, "onProduce() ")
val producerDeferred = launch {
val params = JSONObject("""{"producerTransportId": transport.id, "kind": kind, "rtpParameters": rtpParameters,"appData": appData}""")
val res = socket?.awaitEmit("produce",params)
val data = res?.get(0) as JSONObject
producerId = data.getString("producer_Id")
}
producerDeferred.join()
Log.d(listenerTAG, "producerId inside the coroutine: $producerId"
}
return#async producerId
}
}
this#RoomClient.producerTransport = device.createSendTransport(
sendTransportListener, id,
iceParameters,
iceCandidates,
dtlsParameters
)
}
}
}
And also I am not sure about the way coroutines are used here.Please correct me If I have missed something central.

How can I access header in a service?

I'm trying to handle JWT-authentication in gRPC on my backend. I can extract the JWT in an interceptor but how do I access it in my service? I think it should be done with a CoroutineContextServerInterceptor but this doesn't work:
val jwtKey: Context.Key<String> = Context.key("jwtKey")
fun main() {
ServerBuilder.forPort(8980).intercept(UserInjector).addService(MyService).build().start().awaitTermination()
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return GrpcContextElement(Context.current().withValue(jwtKey, jwtString))
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${jwtKey.get()}")
return Test.MyResponse.getDefaultInstance()
}
}
Output:
coroutineContext: something
testingJWT: null
I think you'll need to propagate that in its own coroutine context element.
class JwtElement(val jwtString: String) : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<JwtElement>
override val key: CoroutineContext.Key<JwtElement>
get() = Key
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return JwtElement(jwtString)
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${coroutineContext[JwtElement]}")
return Test.MyResponse.getDefaultInstance()
}
}

How to emit from a LiveData builder from a non-suspending callback function

I'm new to LiveData and Kotlin Coroutines. I'm trying to use the Chromium Cronet library to make a request from my repository class to return a LiveData object. To return the liveData, I'm using the new LiveData builder (coroutines with LiveData). How would I emit the result from a successful Cronet request?
class CustomRepository #Inject constructor(private val context: Context, private val gson: Gson) : Repository {
private val coroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
override suspend fun getLiveData(): LiveData<List<MyItem>> = liveData(coroutineDispatcher) {
val executor = Executors.newSingleThreadExecutor()
val cronetEngineBuilder = CronetEngine.Builder(context)
val cronetEngine = cronetEngineBuilder.build()
val requestBuilder = cronetEngine.newUrlRequestBuilder(
"http://www.exampleApi.com/example",
CustomRequestCallback(gson),
executor
)
val request: UrlRequest = requestBuilder.build()
request.start()
}
class CustomRequestCallback(private val gson: Gson) : UrlRequest.Callback() {
override fun onReadCompleted(request: UrlRequest?, info: UrlResponseInfo?, byteBuffer: ByteBuffer?) {
byteBuffer?.flip()
byteBuffer?.let {
val byteArray = ByteArray(it.remaining())
it.get(byteArray)
String(byteArray, Charset.forName("UTF-8"))
}.apply {
val myItems = gson.fromJson(this, MyItem::class.java)
// THIS IS WHAT I WANT TO EMIT
// emit(myItems) doesn't work since I'm not in a suspending function
}
byteBuffer?.clear()
request?.read(byteBuffer)
}
// other callbacks not shown
}
}
The solution involves wrapping the UrlRequest.Callback traditional callback structure in a suspendCoroutine builder.
I also captured my learning in a Medium article which discusses Cronet integration with LiveData and Kotlin Coroutines.
override suspend fun getLiveData(): LiveData<List<MyItem>> = liveData(coroutineDispatcher) {
lateinit var result: List<MyItem>
suspendCoroutine<List<MyItem>> { continuation ->
val requestBuilder = cronetEngine.newUrlRequestBuilder(
"http://www.exampleApi.com/example",
object : UrlRequest.Callback() {
// other callbacks not shown
override fun onReadCompleted(request: UrlRequest?, info: UrlResponseInfo?, byteBuffer: ByteBuffer?) {
byteBuffer?.flip()
byteBuffer?.let {
val byteArray = ByteArray(it.remaining())
it.get(byteArray)
String(byteArray, Charset.forName("UTF-8"))
}.apply {
val myItems = gson.fromJson(this, MyItem::class.java)
result = myItems
continuation.resume(result)
}
byteBuffer?.clear()
request?.read(byteBuffer)
},
executor
)
val request: UrlRequest = requestBuilder.build()
request.start()
}
emit(result)
}

Which way is better between companion object and fun without a class in Kotlin?

I know there isn't a static function in Kotlin, so I write two code in OkHttpService.kt and my.kt
I don't know which is better, could you tell me? Thanks!
OkHttpService.kt
class OkHttpService {
companion object {
fun httpGet(username: String, callback: Callback) {
val fetchRepoUrl = "https://api.github.com/users/$username/repos?page=1&per_page=20"
val client = OkHttpClient()
val request = Request.Builder()
.url(fetchRepoUrl)
.build()
client.newCall(request).enqueue(callback)
}
}
}
my.kt
fun OkHttpService_httpGet(username: String, callback: Callback) {
val fetchRepoUrl = "https://api.github.com/users/$username/repos?page=1&per_page=20"
val client = OkHttpClient()
val request = Request.Builder()
.url(fetchRepoUrl)
.build()
client.newCall(request).enqueue(callback)
For scoping use a regular object, not companion:
object OkHttpService{
fun httpGet(username: String, callback: Callback) {
val fetchRepoUrl = "https://api.github.com/users/$username/repos?page=1&per_page=20"
val client = OkHttpClient()
val request = Request.Builder()
.url(fetchRepoUrl)
.build()
client.newCall(request).enqueue(callback)
}
}