Skip to content

No support for non-standard HTTP status codes in reactive ClientHttpResponse [SPR-16748] #21289

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

Closed
spring-projects-issues opened this issue Apr 19, 2018 · 3 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Apr 19, 2018

Spencer Gibb opened SPR-16748 and commented

ClientHttpResponse does not have a way to retrieve http status codes as an int without using the HttpStatus enum. This causes responses with custom status codes to fail with the following exception.

java.lang.IllegalArgumentException: No matching constant for [432]

	at org.springframework.http.HttpStatus.valueOf(HttpStatus.java:520)
	at org.springframework.http.client.reactive.ReactorClientHttpResponse.getStatusCode(ReactorClientHttpResponse.java:72)
	at org.springframework.http.client.reactive.ClientHttpResponseDecorator.getStatusCode(ClientHttpResponseDecorator.java:55)
	at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$exchange$4(ExchangeFunctions.java:85)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:185)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:185)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:185)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108)
	at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onNext(FluxRetryPredicate.java:81)
	at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:140)
	at reactor.ipc.netty.channel.PooledClientContextHandler.fireContextActive(PooledClientContextHandler.java:84)
	at reactor.ipc.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:573)
	at reactor.ipc.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:134)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.logging.LoggingHandler.channelRead(LoggingHandler.java:241)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
	at java.lang.Thread.run(Thread.java:748)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:133)
		at reactor.core.publisher.Mono.block(Mono.java:1199)
		at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:282)
		at org.springframework.cloud.gateway.test.HttpStatusTests.nonStandardCodeWorks(HttpStatusTests.java:70)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
		at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
		at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
		at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
		at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
		at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
		at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
		at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
		at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
		at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
		at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
		at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
		at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
		at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
		at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
		at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
		at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
		at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
		at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
		at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
		at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
		at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
		at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
		at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
		at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
		at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
		at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

ServerHttpResponse also does not have a way to set status codes without using the enum, though AbstractServerHttpResponse does.


Affects: 5.0.5

Reference URL: spring-cloud/spring-cloud-gateway#284

Issue Links:

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've slightly repurposed this ticket for ClientHttpResponse.getRawStatusCode() support, along the lines of the non-reactive ClientHttpResponse equivalent. On the client side, there is a need for handling any kind of response - even invalid ones or unusual ones -, so it really needs to be available in that way there... whereas on the server side, a corresponding setter does not necessarily have to be on the interface (although we may put it there eventually but rather in the context of a different ticket).

@marcingrzejszczak
Copy link
Contributor

It seems that this problem is still there

spring-cloud/spring-cloud-sleuth#1462

java.lang.IllegalArgumentException: No matching constant for [499]
	at org.springframework.http.HttpStatus.valueOf(HttpStatus.java:532) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.http.client.reactive.ReactorClientHttpResponse.getStatusCode(ReactorClientHttpResponse.java:90) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.reactive.function.client.DefaultClientResponse.statusCode(DefaultClientResponse.java:75) ~[spring-webflux-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction$MonoWebClientTrace$WebClientTracerSubscriber.terminateSpan(TraceWebClientBeanPostProcessor.java:349) ~[spring-cloud-sleuth-core-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction$MonoWebClientTrace$WebClientTracerSubscriber.onNext(TraceWebClientBeanPostProcessor.java:300) ~[spring-cloud-sleuth-core-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction$MonoWebClientTrace$WebClientTracerSubscriber.onNext(TraceWebClientBeanPostProcessor.java:232) ~[spring-cloud-sleuth-core-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) ~[spring-cloud-sleuth-core-2.1.4.RELEASE.jar:2.1.4.RELEASE]
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.2.12.RELEASE.jar:3.2.12.RELEASE]
	at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) ~[spring-cloud-sleuth-core-2.1.4.RELEASE.jar:2.1.4.RELEASE]

@robotmrv
Copy link

robotmrv commented Oct 9, 2019

@marcingrzejszczak ,
it seems it should be used
if (clientResponse == null || HttpStatus.resolve(response.rawStatusCode()) == null) {
instead of
if (clientResponse == null || clientResponse.statusCode() == null) {
on TraceWebClientBeanPostProcessor.java:349 because org.springframework.web.reactive.function.client.ClientResponse#statusCode is not nullable according to the javadocs

also clientResponse.statusCode() could allocate new array of HttpStatus and iterates over elements for every invocation (see org.springframework.http.HttpStatus#resolve), maybe it is better to use variable in TraceExchangeFilterFunction.MonoWebClientTrace.WebClientTracerSubscriber#terminateSpan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants