>
you can use set
exchange.getResponse().getHeaders().set(name, value);
or more recommendations way
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri:
filters:
- SetResponseHeader=X-Response-Foo, Bar
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<
@Configuration
@Slf4j
public class RefreshTokenGlobalFilter implements GlobalFilter, Ordered {
@Autowired
@Qualifier(MicroService.AUTH_SERVICE_WEBCLIENT)
private WebClient authWebClient;
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
final var token = JWTUtil.getTokenFromExchange(exchange);
if (token.isEmpty()) {
return chain.filter(exchange);
final var decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
exchange.getSession().subscribe(session -> {
final var userContext = (UserContext) session.getAttributes().get("userContext");
if (userContext != null) {
final var refreshToken = userContext.getRefreshToken();
if (refreshToken != null && JWTUtil.isTokenExpired(token)) {
final Mono<TokenHolderBean> refreshTokenMono = authWebClient.get()
.uri("/auth/refresh-token/{refreshToken}", refreshToken)
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(TokenHolderBean.class);
refreshTokenMono.subscribe(userTokenHolder -> {
log.info("Token Expired.. Setting the new Token");
decoratedResponse.getHeaders().set("Authorization",
"Bearer " + userTokenHolder.getAccessToken());
@Override
public int getOrder() {
return 1;
I got the root cause. Getting the refreshTokenMono is webclient call which is in a different service.. By the time it gives the response, main response is already about to commit and wont allow us to modify the response headers. I think i have to go for a blocking call here. Any otherway is there apart from blocking call?
This is working ..
final var request = exchange.getRequest().mutate().headers(headers -> {
final var session = exchange.getSession().block();
final UserContext userContext = session.getAttribute(GatewayConstants.USER_CONTEXT);
final boolean isGuestUser = isGuestUser(token);
if (userContext != null && token.equals(userContext.getAccessToken())) {
final var refreshToken = userContext.getRefreshToken();
if (refreshToken != null && JWTUtil.isTokenExpired(token)) {
final TokenHolderBean userTokenHolder = authWebClient.get()
.uri("/auth/refresh-token/{refreshToken}", refreshToken).accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(TokenHolderBean.class).block(); //Blocking call to hold the exchange.getResponse for not becoming in a readonly state
userContext.setIdToken(userTokenHolder.getIdToken());
log.info("Token Expired.. Setting the new Token");
exchange.getResponse().getHeaders().add("Authorization",
"Bearer " + userTokenHolder.getAccessToken());
final var idToken = userContext.getIdToken();
logSessionId(exchange, isGuestUser, session, idToken);
addToHeader(headers, GatewayConstants.USER_CONTEXT, new ObjectMapper().valueToTree(userContext));
}).build();
I think there are running into a race condition. you can use AtomicReference with subscribe() to get session content.
AtomicReference<String> sessionRef = new AtomicReference<>();
exchange.getSession().subscribe(session-> {
sessionRef.set(session.getAttribute(GatewayConstants.USER_CONTEXT));
final UserContext userContext = sessionRef.get();
if userContext is null you only can get session with map() , like this
exchange.getSession()
.map(webSession -> (String) webSession.getAttribute(GatewayConstants.USER_CONTEXT))
// next step handle it
you can test it. because I can't reproduce it with you given code.
The following should work.
exchange.getResponse().getHeaders().add(config.getName(), config.getValue());
You are referencing the response before the response has returned.
final var decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse());
Needs to go after
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Also, calling subscribe() on the session probably isn't the way to go. Better to use a map() and let webflux handle the subscription`.
Try this
ServerHttpResponse response = exchange.getResponse();
response.beforeCommit(() -> {
response.getHeaders().set(key,value));
return Mono.empty();
Qlone, AndrewShaw123, westhacker, mgiglio, youmustrust, mliu1002, sdoq19, zcz3313, and aleks-novikov reacted with thumbs up emoji
taccisum reacted with heart emoji
All reactions
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
request.mutate().header("clientId","clientId-first-request");
request.mutate().headers(httpHeaders -> httpHeaders.remove("sid"));
response.beforeCommit(() -> {
response.getHeaders().set("clientId", "clientId-first-response");
return Mono.empty();
System.out.println("first pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("third post filter");
FYI, both beforeCommit and decorator are OK for the problem:
Root cause of this problem:
org.springframework.http.server.reactive.AbstractServerHttpResponse
@Override
public HttpHeaders getHeaders() {
if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
else if (this.state.get() == State.COMMITTED) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
else {
return this.headers;
If the response is not ready but the connection is closed, then the state would be State.COMMITTED and readOnlyHeaders is returned.
beforeCommit add action to commitActions,and before response commit:
org.springframework.http.server.reactive.AbstractServerHttpResponse
protected Mono<Void> doCommit(@Nullable Supplier<? extends Mono<Void>> writeAction) {
Flux<Void> allActions = Flux.empty();
if (this.state.compareAndSet(State.NEW, State.COMMITTING)) {
if (!this.commitActions.isEmpty()) {
allActions = Flux.concat(Flux.fromIterable(this.commitActions).map(Supplier::get))
.doOnError(ex -> {
if (this.state.compareAndSet(State.COMMITTING, State.COMMIT_ACTION_FAILED)) {
getHeaders().clearContentHeaders();
else if (this.state.compareAndSet(State.COMMIT_ACTION_FAILED, State.COMMITTING)) {
// Skip commit actions
else {
return Mono.empty();
allActions = allActions.concatWith(Mono.fromRunnable(() -> {
applyStatusCode();
applyHeaders();
applyCookies();
this.state.set(State.COMMITTED);
if (writeAction != null) {
allActions = allActions.concatWith(writeAction.get());
return allActions.then();
Mono.empty(); will be returned and commitActions will not be executed.
However, if consecutive global filters are dependent on headers, for example, filter 1 set a header, filter 2 read it. Then maybe you should use decorator.