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.