spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.38k stars 38.05k forks source link

WebFlux returns 406 but Spring MVC renders "text/plain" request based on Publisher<?> and String [SPR-17078] #21615

Open spring-projects-issues opened 6 years ago

spring-projects-issues commented 6 years ago

Dave Syer opened SPR-17078 and commented

I assume this is a Framework issue (might be Spring Boot I guess, since that's what I use to test it).

Controller:

@GetMapping("/straight")
public Mono<ResponseEntity<Publisher<?>>> straight() {
     return Mono.just(ResponseEntity.ok().header("x-foo", "spam")
               .body(Mono.from(Flux.just("foo", "bar"))));
}

Test:

@Test
public void straight() throws Exception {
     ResponseEntity<String> result = rest.exchange(RequestEntity
               .get(new URI("/straight")).accept(MediaType.TEXT_PLAIN).build(),
               String.class);
     assertThat(result.getBody()).isEqualTo("foo");
     assertThat(result.getHeaders()).containsKey("x-foo");
}

The test passes with MVC and fails with Webflux (406 not acceptable).


Affects: 5.0.7

spring-projects-issues commented 6 years ago

Rossen Stoyanchev commented

In this case WebFlux depends on generic type information and Publisher<?> does not indicate what values should be expected. I confirmed Publisher<String> does work.

In Spring MVC it works because of the way it resolves async values first, and then dispatches to resume processing with the resolved values, at which point the value type is known. So it happens to work but in general with reactive types the expectation is that element type information needs to be available one way or another.

So this is expected behavior.

spring-projects-issues commented 6 years ago

Dave Syer commented

That's a bit disappointing. Isn't there a way to look at the concrete type at runtime? That way the same behaviour could be supported in Webflux and MVC. I can't change the signature of the controller because it doesn't always return a String.

spring-projects-issues commented 6 years ago

Rossen Stoyanchev commented

Not really because we need element type information in order to select the content type, to then choose an encoder, and only then can we produce values. In WebFlux the element type must be known to encode or decode.

I guess you could say Spring MVC has an advantage in its simpler view of all this where it decides upfront if the media type implies streaming, and if not it waits for the values to be resolved before making any decisions. The downside is that processing has the overhead of the extra async dispatches (1 request to kick off, a second async after unwrapping the Mono around ResponseEntity, and a third async after unwraping the Publisher body for final rendering), each time mapping to the controller, etc. That and the fact that all sync code must be kept separate.

What are the requirements for this controller? Perhaps it can be organized into multiple controller methods based on produces condition?

spring-projects-issues commented 6 years ago

Dave Syer commented

This sucks because if you don't specify a preference (no "accept" header) Spring happily renders the string and sends a 200. The problem then is that it is a JSON string, so it gets quoted, which is daft from a UX point of view. Is there really no way to detect the runtime type? Jackson can do it, apparently, if the JSON case works.

I'll try multiple controller methods as well. But that's kind of mad, and it's a workaround IMO. I potentially have to add a new method to the controller for every single media type.

spring-projects-issues commented 6 years ago

Rossen Stoyanchev commented

On further thought, we probably could update AbstractMessageWriterResultHandler for a single value Publisher to first resolve the value, before deciding how to render.