如何在 graphql-java 中的解析器设置响应标头

问题描述 投票:0回答:1

我正在使用 graphql-java 20.0(在 Kotlin 中的 spring-boot-starter-graphql 中),并且想要将自定义标头添加到我的解析器的响应中。目前,我的解析器仅返回应包含在 graphql 响应中的实体(类型为

MyEntity
) - 有效:

@QueryMapping
fun myResolver(
    @Argument arg1: String,
    @Argument arg2: MyInput,
): MyEntity = service.getMyEntity(arg1, arg2)

我尝试按照这个SO答案中的建议将其更改为ResponseEntity,但这显然不是由graphql-java处理的,导致我的响应数据为null

/* this does not work, since graphql-java doe not map the ResponseEntity's body and results in a graphql response with null in the data object */ @QueryMapping fun myResolver( @Argument arg1: String, @Argument arg2: MyInput, ): ResponseEntity<MyEntity> = ResponseEntity.ok().run { this.header("X-My-Header", "whatever") }.body(service.getMyEntity(arg1, arg2))
但是,我找不到任何替代方案可以让我在响应旁边设置自定义标头。在 StackOverflow 上,我只找到了 

LaravelAstro 的答案。在 graphql-spring-boot 仓库中,有一个“类似的问题”近两年来一直没有得到解答。 有人知道在我的 graphql 解析器中设置自定义标头的方法吗?我需要这样做,因为我的标头需要根据某些请求属性而有所不同。

如果有人找到更干净的解决方案,请告诉我。但就目前而言,这是可行的:
spring-boot graphql-java
1个回答
0
投票
我创建了一个 ThreadLocal 来保存标头值:

object GraphQLMyHeaderThreadLocalStorage { private val context = ThreadLocal<String>() var value: String? get() = context.get() set(value) = value?.let { context.set(it) } ?: context.remove() fun clear() = context.remove() }

在我的解析器中,我现在可以使用我的请求特定值设置此 ThreadLocal:
@QueryMapping
fun myResolver(
    @Argument arg1: String,
    @Argument arg2: MyInput,
): MyEntity = service.getMyEntity(arg1, arg2).also {
    GraphQLMyHeaderThreadLocalStorage.value = "whatever inferred from ${it}"
}

如果我提前包装并在
Filter
之后进行修改,我仍然可以在

chain.doFilter()

中修改我的回复:
class GraphQLMyHeaderFilter : Filter {
    @Throws(IOException::class, ServletException::class)
    override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
        if (!response.isCommitted) {
            val responseWrapper = object : HttpServletResponseWrapper(response as HttpServletResponse) {
                fun updateMyHeader(value: String?) {
                    if (value != null) {
                        setHeader("X-My-Header", value)
                    } else {
                        setHeader("X-My-Header", "default value")
                    }
                }
            }
            chain.doFilter(request, responseWrapper)

            // modify the response after the resolver was called
            if (!response.isCommitted) {
                val headerValue = try {
                    GraphQLMyHeaderThreadLocalStorage.value
                } finally {
                    GraphQLMyHeaderThreadLocalStorage.clear()
                }
                responseWrapper.updateCacheControl(headerValue)
            }
        } else {
            chain.doFilter(request, response)
        }
    }
}

@Configuration
class FilterConfig {
    @Bean
    fun graphQLMyHeaderFilter(): FilterRegistrationBean<GraphQLMyHeaderFilter> {
        val registrationBean = FilterRegistrationBean<GraphQLMyHeaderFilter>()
        registrationBean.filter = GraphQLMyHeaderFilter()
        registrationBean.addUrlPatterns("/graphql")
        return registrationBean
    }
}

备注:

response.isCommitted
    检查实际上在我的实验中是不必要的,但我宁愿安全也不后悔。
  • 我将过滤器限制为仅
    FilterConfig
  • 中的“/graphql”端点。要将其应用到所有端点,您可以使用
  • "/*"
     模式代替 
    "/graphql"
    ,或者删除 
    FilterConfig
     并用 
    GraphQLMyHeaderFilter
     注释 
    @Component
    确保之后始终
    GraphQLMyHeaderThreadLocalStorage.clear()
  • ,这样状态就不会泄漏到后续请求中。
  • Filter
  • 是我发现在调用解析器后仍然可以修改(未提交)响应的唯一选项。在我的实验中,甚至没有针对 GraphQL 请求调用
  • ResponseBodyAdvice
    HandlerInterceptor
     已被访问,但 
    HandlerInterceptor.preHandle()
     在解析器之前执行(
    两次偶数
    )并且 
    HandlerInterceptor.postHandle() 收到已提交的响应(即,无法再修改响应)。
        
© www.soinside.com 2019 - 2024. All rights reserved.