-
Notifications
You must be signed in to change notification settings - Fork 362
feat: Add subscriptions support to ktor server #1774
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
Changes from 1 commit
8cdb1fb
ecaca67
ef75365
a1f3d7b
29f0f2d
84b7e5a
442882d
4e31b28
79507a9
082564e
a68716c
8b005e4
337e90e
844a3b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright 2023 Expedia, Inc | ||
* | ||
* Licensed 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.expediagroup.graphql.examples.server.ktor.schema | ||
|
||
import com.expediagroup.graphql.generator.annotations.GraphQLDescription | ||
import com.expediagroup.graphql.server.operations.Subscription | ||
import graphql.GraphqlErrorException | ||
import graphql.execution.DataFetcherResult | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.flow | ||
import kotlinx.coroutines.flow.flowOf | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.reactive.asPublisher | ||
import org.reactivestreams.Publisher | ||
import kotlin.random.Random | ||
|
||
class ExampleSubscriptionService : Subscription { | ||
|
||
@GraphQLDescription("Returns a single value") | ||
fun singleValue(): Flow<Int> = flowOf(1) | ||
|
||
@GraphQLDescription("Returns stream of values") | ||
fun multipleValues(): Flow<Int> = flowOf(1, 2, 3) | ||
|
||
@GraphQLDescription("Returns a random number every second") | ||
suspend fun counter(limit: Int? = null): Flow<Int> = flow { | ||
var count = 0 | ||
while (true) { | ||
count++ | ||
if (limit != null) { | ||
if (count > limit) break | ||
} | ||
emit(Random.nextInt()) | ||
delay(1000) | ||
} | ||
} | ||
|
||
@GraphQLDescription("Returns a random number every second, errors if even") | ||
fun counterWithError(): Flow<Int> = flow { | ||
while (true) { | ||
val value = Random.nextInt() | ||
if (value % 2 == 0) { | ||
throw Exception("Value is even $value") | ||
} else emit(value) | ||
delay(1000) | ||
} | ||
} | ||
|
||
@GraphQLDescription("Returns one value then an error") | ||
fun singleValueThenError(): Flow<Int> = flowOf(1, 2) | ||
.map { if (it == 2) throw Exception("Second value") else it } | ||
|
||
@GraphQLDescription("Returns stream of errors") | ||
fun flowOfErrors(): Publisher<DataFetcherResult<String?>> { | ||
val dfr: DataFetcherResult<String?> = DataFetcherResult.newResult<String?>() | ||
.data(null) | ||
.error(GraphqlErrorException.newErrorException().cause(Exception("error thrown")).build()) | ||
.build() | ||
|
||
return flowOf(dfr, dfr).asPublisher() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,18 +17,15 @@ | |
package com.expediagroup.graphql.server.ktor | ||
|
||
import com.expediagroup.graphql.generator.extensions.print | ||
import com.expediagroup.graphql.server.ktor.subscriptions.KtorGraphQLSubscriptionHandler | ||
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 | ||
import io.ktor.server.routing.Route | ||
import io.ktor.server.routing.application | ||
import io.ktor.server.routing.get | ||
import io.ktor.server.routing.post | ||
import io.ktor.http.* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please don't use wildcard imports, use single import per line -> this comment applies to all other files as well |
||
import io.ktor.serialization.jackson.* | ||
import io.ktor.server.application.* | ||
import io.ktor.server.plugins.contentnegotiation.* | ||
import io.ktor.server.response.* | ||
import io.ktor.server.routing.* | ||
import io.ktor.server.websocket.* | ||
|
||
/** | ||
* Configures GraphQL GET route | ||
|
@@ -70,6 +67,27 @@ fun Route.graphQLPostRoute(endpoint: String = "graphql", streamingResponse: Bool | |
return route | ||
} | ||
|
||
/** | ||
* Configures GraphQL subscriptions route | ||
* | ||
* @param endpoint GraphQL server subscriptions endpoint, defaults to 'subscriptions' | ||
* @param handlerOverride Alternative KtorGraphQLSubscriptionHandler to handle subscriptions logic | ||
*/ | ||
fun Route.graphQLSubscriptionsRoute( | ||
endpoint: String = "subscriptions", | ||
protocol: String? = null, | ||
handlerOverride: KtorGraphQLSubscriptionHandler? = null, | ||
) { | ||
val handler = handlerOverride ?: run { | ||
val graphQLPlugin = this.application.plugin(GraphQL) | ||
graphQLPlugin.subscriptionsHandler | ||
} | ||
|
||
webSocket(path = endpoint, protocol = protocol) { | ||
handler.handle(this) | ||
} | ||
} | ||
|
||
/** | ||
* Configures GraphQL SDL route. | ||
* | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -18,6 +18,7 @@ package com.expediagroup.graphql.server.ktor | |||
|
||||
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler | ||||
import com.expediagroup.graphql.server.execution.GraphQLServer | ||||
import com.expediagroup.graphql.server.ktor.subscriptions.KtorGraphQLSubscriptionHandler | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. guessing this will fail linting as it is not used in the file
Suggested change
|
||||
import io.ktor.server.request.ApplicationRequest | ||||
|
||||
/** | ||||
|
@@ -26,5 +27,5 @@ import io.ktor.server.request.ApplicationRequest | |||
class KtorGraphQLServer( | ||||
requestParser: KtorGraphQLRequestParser, | ||||
contextFactory: KtorGraphQLContextFactory, | ||||
requestHandler: GraphQLRequestHandler | ||||
requestHandler: GraphQLRequestHandler, | ||||
) : GraphQLServer<ApplicationRequest>(requestParser, contextFactory, requestHandler) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright 2023 Expedia, Inc | ||
* | ||
* Licensed 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.expediagroup.graphql.server.ktor.subscriptions | ||
|
||
import io.ktor.server.websocket.* | ||
|
||
interface KtorGraphQLSubscriptionHandler { | ||
suspend fun handle(session: WebSocketServerSession) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make it lazily created? not everyone needs subscriptions so this object may never be used