If you setup functional endpoints as described in the documentation, like so:

val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        POST("/person", handler::createPerson)
    }
}

... then you try to add a response header of X-My-Response-Header with a value of foo to these routes using filter or after, like so:

val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        POST("/person", handler::createPerson)
        after { request, response -> ServerResponse.from(response).header("X-My-Response-Header", "foo").build() }
    }
}

... you'll notice your header gets added, but your body is gone. This is because ServerResponse.from() doesn't copy the body, only the status code and responses. From the docs:

Create a builder with the status code and headers of the given response.

So say you eschew immutability and you try to mutate it:

val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        POST("/person", handler::createPerson)
        after { request, response -> response.headers().set("X-My-Response-Header", "foo"); response }
    }
}

... this will throw an exception. That's because ServerResponse is (rightly) immutable and uses ReadOnlyHttpHeaders, and that implementation of HttpHeaders will (rightly) throw an exception if you attempt to modify it in any way.

The combination of these two limitations means you cannot add a response header in filters because: 1. You can't create a new ServerResponse with the new header and fetch the body of the previous ServerResponses intact (outside of some extreme measures). 2. You can't mutate the existing ServerResponse to have the new header.

Comment From: poutsma

You can copy the raw contents from the original response like so:

after { request, response -> ServerResponse.from(response).header("X-My-Response-Header", "foo").body(response.bodyToFlux(DataBuffer.class)) }

Comment From: carusology

@poutsma There isn't a bodyToFlux() method on ServerResponse (Source). That's on ServerRequest (Source)

Comment From: poutsma

I'm sorry for closing the issue prematurely.

It looks like there is no way to accomplish this with after, but you can use filter:

.filter((request, next) ->
    next.handle(request). // invoke the next handler in the chain
        flatMap(response ->
        ServerResponse.status(response.rawStatusCode()) // recreate the response
        .headers(headers -> {
        headers.addAll(response.headers());
        headers.add("X-My-Response-Header", "foo");
        })
        .build(response::writeTo)))
.build();

Sorry about using Java; my Kotlin is not that great. Hopefully you will be able to convert it.