Skip to content

WebClient's handling of empty bodies seems broken [SPR-15679] #20238

@spring-projects-issues

Description

@spring-projects-issues

Johannes Edmeier opened SPR-15679 and commented

When the WebClient retrieves an response without content (header content-length: 0 and without content-type header ) I would expect following things:

  1. when calling exchange() and doing ClientResponse#bodyToMono(Void.class) I expect that an empty Mono (that just completes) for the body is returned
  2. when calling retrieve().toEntity(Void.class) I expect a Mono with a ResponseEntity instance whose hasBody() returns false.

Both is currently not possible and throws an UnsupportedMediaTypeException

Tests:

  @Test
    public void nocontent_entity() {
        Mono<ResponseEntity<Void>> responseEntity = WebClient.create("http://localhost:8080")
                                                             .post()
                                                             .uri("/nocontent")
                                                             .retrieve()
                                                             .toEntity(Void.class);

        StepVerifier.create(responseEntity).assertNext(r -> {
            assertThat(r.hasBody()).isFalse();
            assertThat(r.getStatusCode().is2xxSuccessful()).isTrue();
        }).verifyComplete();
    }

    @Test
    public void nocontent_response() {
        Mono<ClientResponse> response = WebClient.create("http://localhost:8080").post().uri("/nocontent").exchange();

        StepVerifier.create(response).assertNext(r -> {
            assertThat(r.statusCode().is2xxSuccessful()).isTrue();
            StepVerifier.create(r.bodyToMono(Void.class)).verifyComplete();
        }).verifyComplete();
    }

Application

@SpringBootApplication
@RestController
public class WebfluxApplication {

	public static void main(String[] args) {
		SpringApplication.run(WebfluxApplication.class, args);
	}

	@PostMapping(path="/nocontent")
	public Mono<Void> nocontent() {
		return Mono.empty();
	}
}
java.lang.AssertionError: expectation "expectComplete" failed (expected: onComplete(); actual: onError(org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/octet-stream' not supported))

	at reactor.test.DefaultStepVerifierBuilder.failPrefix(DefaultStepVerifierBuilder.java:2114)
	at reactor.test.DefaultStepVerifierBuilder.fail(DefaultStepVerifierBuilder.java:2110)
	at reactor.test.DefaultStepVerifierBuilder.lambda$expectComplete$3(DefaultStepVerifierBuilder.java:199)
	at reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:1855)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1241)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1186)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onError(DefaultStepVerifierBuilder.java:858)
	at reactor.core.publisher.Operators.error(Operators.java:195)
	at reactor.core.publisher.MonoError.subscribe(MonoError.java:51)
	at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:665)
	at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:639)
	at reactor.test.DefaultStepVerifierBuilder.verifyComplete(DefaultStepVerifierBuilder.java:508)
	at com.example.webflux.WebfluxApplicationTests.lambda$nocontent_response$1(WebfluxApplicationTests.java:46)
	at reactor.test.DefaultStepVerifierBuilder.lambda$consumeNextWith$1(DefaultStepVerifierBuilder.java:157)
	at reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:1855)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1241)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1186)
	at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onNext(DefaultStepVerifierBuilder.java:875)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:170)
	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:79)
	at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:122)
	at reactor.ipc.netty.channel.PooledClientContextHandler.fireContextActive(PooledClientContextHandler.java:84)
	at reactor.ipc.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:542)
	at reactor.ipc.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:125)
	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.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
	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:1334)
	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:926)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
	at java.lang.Thread.run(Thread.java:745)
	Suppressed: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/octet-stream' not supported
		at org.springframework.web.reactive.function.BodyExtractors.lambda$readWithMessageReaders$16(BodyExtractors.java:237)
		at java.util.Optional.orElseGet(Optional.java:267)
		at org.springframework.web.reactive.function.BodyExtractors.readWithMessageReaders(BodyExtractors.java:233)
		at org.springframework.web.reactive.function.BodyExtractors.lambda$toMono$1(BodyExtractors.java:92)
		at org.springframework.web.reactive.function.client.DefaultClientResponse.body(DefaultClientResponse.java:78)
		at org.springframework.web.reactive.function.client.DefaultClientResponse.bodyToMono(DefaultClientResponse.java:98)
		... 34 more

Affects: 5.0 RC2

Issue Links:

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions