Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade to ktor 3 #2055

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ kotlinx-benchmark = "0.4.11"
kotlinx-coroutines = "1.8.1"
# TODO kotlin 1.9 upgrade: fix GraphQLTestUtils and GenerateKotlinxClientIT
kotlinx-serialization = "1.6.3"
ktor = "2.3.12"
ktor = "3.0.1"
fastjson2 = "2.0.53"
maven-plugin-annotation = "3.13.1"
maven-plugin-api = "3.9.8"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import com.expediagroup.graphql.server.execution.subscription.GRAPHQL_WS_PROTOCO
import com.fasterxml.jackson.databind.ObjectMapper
import io.ktor.http.ContentType
import io.ktor.serialization.jackson.jackson
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.application.plugin
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respondText
Expand Down Expand Up @@ -115,11 +113,11 @@ fun Route.graphiQLRoute(
graphQLEndpoint: String = "graphql",
subscriptionsEndpoint: String = "subscriptions",
): Route {
val contextPath = this.environment?.rootPath
val contextPath = this.application.rootPath
val graphiQL = GraphQL::class.java.classLoader.getResourceAsStream("graphql-graphiql.html")?.bufferedReader()?.use { reader ->
reader.readText()
.replace("\${graphQLEndpoint}", if (contextPath.isNullOrBlank()) graphQLEndpoint else "$contextPath/$graphQLEndpoint")
.replace("\${subscriptionsEndpoint}", if (contextPath.isNullOrBlank()) subscriptionsEndpoint else "$contextPath/$subscriptionsEndpoint")
.replace("\${graphQLEndpoint}", if (contextPath.isBlank()) graphQLEndpoint else "$contextPath/$graphQLEndpoint")
.replace("\${subscriptionsEndpoint}", if (contextPath.isBlank()) subscriptionsEndpoint else "$contextPath/$subscriptionsEndpoint")
} ?: throw IllegalStateException("Unable to load GraphiQL")
return get(endpoint) {
call.respondText(graphiQL, ContentType.Text.Html)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fun StatusPagesConfig.defaultGraphQLStatusPages(): StatusPagesConfig {
exception<Throwable> { call, cause ->
when (cause) {
is UnsupportedOperationException -> call.respond(HttpStatusCode.MethodNotAllowed)
is InvalidPayloadException -> call.respond(HttpStatusCode.UnsupportedMediaType)
else -> call.respond(HttpStatusCode.BadRequest)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ internal const val REQUEST_PARAM_QUERY = "query"
internal const val REQUEST_PARAM_OPERATION_NAME = "operationName"
internal const val REQUEST_PARAM_VARIABLES = "variables"

class InvalidPayloadException(message: String, throwable: Throwable) : IllegalStateException(message, throwable)

/**
* GraphQL Ktor [ApplicationRequest] parser.
*/
Expand Down Expand Up @@ -62,6 +64,6 @@ open class KtorGraphQLRequestParser(
private suspend fun parsePostRequest(request: ApplicationRequest): GraphQLServerRequest? = try {
request.call.receive()
} catch (e: IOException) {
throw IllegalStateException("Invalid HTTP request - unable to parse GraphQL request from POST payload", e)
throw InvalidPayloadException("Invalid HTTP request - unable to parse GraphQL request from POST payload", e)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import io.ktor.http.contentType
import io.ktor.serialization.jackson.jackson
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.config.*
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.routing.Routing
import io.ktor.server.routing.*
import io.ktor.server.testing.testApplication
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
Expand Down Expand Up @@ -104,7 +105,7 @@ class GraphQLPluginTest {
flow: Int!
}
""".trimIndent()
testApplication {
testModule {
val response = client.get("/sdl")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals(expectedSchema, response.bodyAsText().trim())
Expand All @@ -113,7 +114,7 @@ class GraphQLPluginTest {

@Test
fun `server should handle valid GET requests`() {
testApplication {
testModule {
val response = client.get("/graphql") {
parameter("query", "query HelloQuery(\$name: String){ hello(name: \$name) }")
parameter("operationName", "HelloQuery")
Expand All @@ -126,7 +127,7 @@ class GraphQLPluginTest {

@Test
fun `server should return Method Not Allowed for Mutation GET requests`() {
testApplication {
testModule {
val response = client.get("/graphql") {
parameter("query", "mutation { foo }")
}
Expand All @@ -136,15 +137,15 @@ class GraphQLPluginTest {

@Test
fun `server should return Bad Request for invalid GET requests`() {
testApplication {
testModule {
val response = client.get("/graphql")
assertEquals(HttpStatusCode.BadRequest, response.status)
}
}

@Test
fun `server should handle valid POST requests`() {
testApplication {
testModule {
val client = createClient {
install(ContentNegotiation) {
jackson()
Expand All @@ -161,7 +162,7 @@ class GraphQLPluginTest {

@Test
fun `server should handle valid POST batch requests`() {
testApplication {
testModule {
val client = createClient {
install(ContentNegotiation) {
jackson()
Expand All @@ -185,7 +186,7 @@ class GraphQLPluginTest {

@Test
fun `server should return Bad Request for invalid POST requests with correct content type`() {
testApplication {
testModule {
val response = client.post("/graphql") {
contentType(ContentType.Application.Json)
}
Expand All @@ -195,15 +196,15 @@ class GraphQLPluginTest {

@Test
fun `server should return Unsupported Media Type for POST requests with invalid content type`() {
testApplication {
testModule {
val response = client.post("/graphql")
assertEquals(HttpStatusCode.UnsupportedMediaType, response.status)
}
}

@Test
fun `server should handle subscription requests`() {
testApplication {
testModule {
val client = createClient {
install(ContentNegotiation) {
jackson()
Expand Down Expand Up @@ -233,7 +234,7 @@ class GraphQLPluginTest {

@Test
fun `server should provide GraphiQL endpoint`() {
testApplication {
testModule {
val response = client.get("/graphiql")
assertEquals(HttpStatusCode.OK, response.status)

Expand All @@ -260,11 +261,19 @@ fun Application.testGraphQLModule() {
}
}
install(io.ktor.server.websocket.WebSockets)
install(Routing) {
routing {
graphQLGetRoute()
graphQLPostRoute()
graphQLSubscriptionsRoute()
graphQLSDLRoute()
graphiQLRoute()
}
}

private fun testModule(block: suspend io.ktor.server.testing.ApplicationTestBuilder.() -> kotlin.Unit) = testApplication {
environment {
config = ApplicationConfig(("application.conf"))
}
block()
}