Skip to content

Commit

Permalink
Add CoroutineHttpService for handle request within Coroutine suspend (#…
Browse files Browse the repository at this point in the history
…5603)

Motivation:
- Closes #5442

Modifications:
- Add CoroutineHttpService implements HttpService

Result:
- Closes #5442.
- You can now use `CoroutineHttpService` that runs your service in a coroutine scope.
Co-authored-by: minwoox <songmw725@gmail.com>
  • Loading branch information
pinest94 and minwoox committed May 10, 2024
1 parent 8ddf422 commit 43ab067
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2024 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.linecorp.armeria.server.kotlin

import com.linecorp.armeria.common.HttpRequest
import com.linecorp.armeria.common.HttpResponse
import com.linecorp.armeria.common.kotlin.CoroutineContexts
import com.linecorp.armeria.common.kotlin.asCoroutineContext
import com.linecorp.armeria.server.HttpService
import com.linecorp.armeria.server.ServiceRequestContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.future.future
import kotlin.coroutines.EmptyCoroutineContext

/**
* A Coroutine-based [HttpService]
*/
fun interface CoroutineHttpService : HttpService {
/**
* Calls [suspendedServe] in a [CoroutineScope] and returns the result as an [HttpResponse].
*/
override fun serve(
ctx: ServiceRequestContext,
req: HttpRequest,
): HttpResponse {
val userContext = CoroutineContexts.get(ctx) ?: EmptyCoroutineContext
return HttpResponse.of(
CoroutineScope(
ctx.asCoroutineContext() + userContext,
).future {
suspendedServe(ctx, req)
},
)
}

/**
* Serves an incoming [HttpRequest] in a [CoroutineScope].
*/
suspend fun suspendedServe(
ctx: ServiceRequestContext,
req: HttpRequest,
): HttpResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.linecorp.armeria.server.kotlin

import com.linecorp.armeria.common.HttpResponse
import com.linecorp.armeria.common.HttpStatus
import com.linecorp.armeria.server.ServerBuilder
import com.linecorp.armeria.server.ServiceRequestContext
import com.linecorp.armeria.testing.junit5.server.ServerExtension
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

class CoroutineHttpServiceTest {
companion object {
@JvmField
@RegisterExtension
val server =
object : ServerExtension() {
override fun configure(sb: ServerBuilder) {
sb.service(
"/hello",
CoroutineHttpService { ctx, req ->
assertContextPropagation()
HttpResponse.of("hello world")
},
).decorator(
CoroutineContextService.newDecorator { ctx ->
CoroutineName("my-coroutine-name")
},
)
}
}

private suspend fun assertContextPropagation() {
assertThat(ServiceRequestContext.currentOrNull()).isNotNull()
assertThat(currentCoroutineContext()[CoroutineName]?.name).isEqualTo("my-coroutine-name")
}
}

@Test
fun `Should return hello world when call hello coroutine service`() =
runTest {
val response = server.blockingWebClient().get("/hello")
assertThat(response.status()).isEqualTo(HttpStatus.OK)
assertThat(response.contentUtf8()).isEqualTo("hello world")
}
}

0 comments on commit 43ab067

Please sign in to comment.