【问题标题】:How to write a resolver for a GraphQL subscription using graphql-java and graphql-java-servlet?如何使用 graphql-java 和 graphql-java-servlet 为 GraphQL 订阅编写解析器?
【发布时间】:2019-07-14 13:56:19
【问题描述】:

我尝试设置一个基于 Spring Boot 的 GraphQL 服务(基于项目 graphql-spring-boot-starterhttps://github.com/graphql-java-kickstart/graphql-spring-boot),它使用 graphql-java-tools,但手动/以编程方式构建 RuntimeWiring。 GraphQL 查询和突变以这种方式工作得很好。但是,我无法让订阅正常工作。

我添加了以下连接来绑定订阅的 DataFetcher:

RuntimeWiring.Builder runtimeWiringBuilder =
    newRuntimeWiring()
    ...
        .type(TypeRuntimeWiring.newTypeWiring("Subscription")
            .dataFetcher("stockQuotes", gqlService::stockQuotes)
        );

我的解析器函数正确返回org.reactivestream.Publisher

Publisher<StockPriceUpdate> stockQuotes(DataFetchingEnvironment env) {
    return stockTickerPublisher.getPublisher();
}

但是,如果我从 GraphiQL 或 GraphQL Playground 调用我的订阅,我会收到以下错误: “没有为类 graphql.execution.reactive.CompletionStageMappingPublisher 找到序列化程序”(下面的堆栈跟踪)。

看起来来自 lib graphql-java-servletGraphQLObjectMapper 尝试将结果(包装的)org.reactivestream.Publisher 直接序列化为 JSON 结果。当然,这没有意义。相反,它应该作为监听器订阅发布者,并在发布者发出下一个更新时通过 WebSocket 推送更新。

据我了解,graphql-java-servlet 已经内置了对通过 WebSockets 订阅的支持。不幸的是,没有可用的普通示例,它可以在不使用 graphql-tools 的情况下进行订阅

有什么想法可以让这个工作吗?非常感谢!

我的application.yml 文件的相关部分是:

server:
  port: 9200

graphql:
  servlet:
    mapping: /graphql
    enabled: true
    corsEnabled: true
    websocket:
      enabled: true
      path: /graphql
    subscriptions:
      websocket:
        path: /graphql

架构(摘录):

schema {
  query: Query
  mutation: Mutation
  subscription : Subscription
}

type Subscription {
  stockQuotes: StockPriceUpdate!
}

type StockPriceUpdate {
  dateTime : String
  stockCode : String
  stockPrice : Float
  stockPriceChange : Float
}

堆栈跟踪:

java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class graphql.execution.reactive.CompletionStageMappingPublisher and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.LinkedHashMap["data"])
    at graphql.servlet.GraphQLObjectMapper.serializeResultAsJson(GraphQLObjectMapper.java:95) ~[graphql-java-servlet-7.1.1.jar:na]
    at graphql.servlet.AbstractGraphQLHttpServlet.query(AbstractGraphQLHttpServlet.java:339) ~[graphql-java-servlet-7.1.1.jar:na]
    at graphql.servlet.AbstractGraphQLHttpServlet.lambda$init$4(AbstractGraphQLHttpServlet.java:209) ~[graphql-java-servlet-7.1.1.jar:na]
    at graphql.servlet.AbstractGraphQLHttpServlet.doRequest(AbstractGraphQLHttpServlet.java:306) ~[graphql-java-servlet-7.1.1.jar:na]
    at graphql.servlet.AbstractGraphQLHttpServlet.doRequestAsync(AbstractGraphQLHttpServlet.java:297) ~[graphql-java-servlet-7.1.1.jar:na]
    at graphql.servlet.AbstractGraphQLHttpServlet.doPost(AbstractGraphQLHttpServlet.java:327) ~[graphql-java-servlet-7.1.1.jar:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90) ~[spring-boot-actuator-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:117) ~[spring-boot-actuator-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:106) ~[spring-boot-actuator-2.1.2.RELEASE.jar:2.1.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_192]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_192]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_192]
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class graphql.execution.reactive.CompletionStageMappingPublisher and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.LinkedHashMap["data"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:313) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:718) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:639) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:33) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3905) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3219) ~[jackson-databind-2.9.7.jar:2.9.7]
    at graphql.servlet.GraphQLObjectMapper.serializeResultAsJson(GraphQLObjectMapper.java:93) ~[graphql-java-servlet-7.1.1.jar:na]
    ... 57 common frames omitted

【问题讨论】:

  • 你试过这个and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)吗?
  • 我无法配置 ObjectMapper,因为它被 graphql-servlet 库内部使用。但如上所述:无论如何,Publisher 被序列化为 JSON 是没有任何意义的。

标签: java spring spring-boot graphql


【解决方案1】:

当您尝试使用自 2018 年 3 月 22 日 (v.0.7.2) 以来未更新的基于电子的 GraphiQL.app 时,会出现上述堆栈跟踪。因此,要解决问题并查看订阅是否正常工作:

  1. 不要使用GraphiQL.app

  2. 将以下依赖项添加到您的 pom.xml 以从您的 Spring Boot 微服务公开重新发送的 /graphiql 端点(用于测试):

        <!-- Graph(i)QL tool: for development -->
        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphiql-spring-boot-starter</artifactId>
            <version>5.4.1</version>
        </dependency>
  1. graphiql 默认情况下期望订阅通过/subscriptions 端点公开。所以:

    • 要么将 graphql.subscriptions.websocket.path/graphql 更改为 /subscriptions

    • 或在您的application.yml 中自定义graphiql(以反映graphql 配置):

graphiql:
  endpoint:
    subscriptions: /graphql

重启后可以去http://localhost:9200/graphiql运行订阅,如下:

subscription StockCodeSubscription {
    stockQuotes {
        dateTime
        stockCode
        stockPrice
        stockPriceChange
    }
}

应该可以!

【讨论】:

  • 谢谢!那是丢失的信息。我实际上使用了 GraphiQL.app。并且还尝试了基于 Web 的 GraphiQL,但不知道它侦听另一个订阅端点。
  • @vaa 您能否告诉我,当我的 Spring Boot 应用程序有上下文路径时,如何使订阅工作?默认情况下,我的订阅网址是 http://localhost:8080/subscriptions 。我尝试设置属性graphql.servlet.subscriptions.websocket.path=/&lt;&lt;context&gt;&gt;/subscriptions,但它仍然调用http://localhost:8080/subscriptions
  • 根据graphql-spring-boot-autoconfigure 中的GraphQLWebsocketAutoConfigurationGraphQLWsServerEndpointRegistration 类,websocketPath 的值来自graphql.servlet.subscriptions.websocket.path,必须从根/ 进行限定。因此,您必须在配置中指定您的 &lt;&lt;context&gt;&gt;。抱歉,稍后回复。
猜你喜欢
  • 2022-09-29
  • 2018-08-06
  • 2019-10-22
  • 1970-01-01
  • 2023-04-08
  • 2019-09-27
  • 2017-09-01
  • 2020-11-17
  • 2020-08-19
相关资源
最近更新 更多