version
- Spring Boot 2.6.2
code
@SpringBootApplication
class FluxErrorHandlingApplication
fun main(args: Array<String>) {
runApplication<FluxErrorHandlingApplication>(*args)
}
@Configuration
class Config {
@Bean
fun router(handler: Handler) =
org.springframework.web.reactive.function.server.router {
GET("/error", handler::handle)
}
}
@Component
class Handler {
fun handle(request: ServerRequest): Mono<ServerResponse> {
throw RuntimeException()
}
}
result
curl -v localhost:8080/error master ✭ ✚ ✱
* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< Content-Type: application/json
< Content-Type: application/json
< Content-Length: 131
Comment From: wilkinsona
Here's a more minimal sample that takes Kotlin out of the picture:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
@SpringBootApplication
public class Gh29179Application {
public static void main(String[] args) {
SpringApplication.run(Gh29179Application.class, args);
}
@Configuration
class Config {
@Bean
RouterFunction<ServerResponse> router(Handler handler) {
return route(GET("/"), handler::handle);
}
}
@Component
class Handler {
Mono<ServerResponse> handle(ServerRequest request) {
throw new RuntimeException();
}
}
}
The duplicate header remains:
$ curl -i localhost:8080
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Type: application/json
Content-Length: 126
{"timestamp":"2022-01-04T16:05:38.216+00:00","path":"/","status":500,"error":"Internal Server Error","requestId":"13e4496c-1"}
Comment From: wilkinsona
This appears to be a regression in Spring Framework 5.3.14. Downgrading to 5.3.13 results in a single Content-Type header. I believe that https://github.com/spring-projects/spring-framework/commit/2a5713f389e305300d7659887d38a89aa4e7e91e is the cause.
Comment From: ryanb93
Combined with https://github.com/istio/istio/issues/30470 this causes clients to receive Content-Type: application/json,application/json;charset=UTF-8 - which clients fail to parse.
Comment From: poutsma
The cause here seems to be in NettyHeadersAdapter, which implements putAll by adding headers instead of setting (and overwriting) them.
This bug seems to have been in NettyHeadersAdapter since the type was introduced, and the changes in https://github.com/spring-projects/spring-framework/commit/2a5713f389e305300d7659887d38a89aa4e7e91e only brought them to light.