Hi,
Could you please help me for how to modify the response headers from Global Filter.
When i use, serverWebExchange.getResponse().getHeaders().add(headerName, headerValue)
it is throwing UnsupportedOperationException from UnsupportedOperationException.
Thanks in advance for your help
exception stack trace while modifying the response header.
java.lang.UnsupportedOperationException: null
at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:67) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at com.oito.gateway.filter.RefreshTokenGlobalFilter.lambda$1(RefreshTokenGlobalFilter.java:63) ~[main/:na]
at reactor.core.publisher.LambdaMonoSubscriber.onNext(LambdaMonoSubscriber.java:137) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:97) ~[spring-cloud-sleuth-core-2.1.0.RC3.jar:2.1.0.RC3]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1476) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1476) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.MonoSingle$SingleSubscriber.onComplete(MonoSingle.java:171) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:794) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:560) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:540) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onComplete(FluxFlatMap.java:426) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.DrainUtils.postCompleteDrain(DrainUtils.java:131) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.DrainUtils.postComplete(DrainUtils.java:186) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxMapSignal$FluxMapSignalSubscriber.onComplete(FluxMapSignal.java:213) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:113) ~[spring-cloud-sleuth-core-2.1.0.RC3.jar:2.1.0.RC3]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:378) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:202) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:343) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:325) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:372) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:511) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:141) ~[reactor-netty-0.8.3.RELEASE.jar:0.8.3.RELEASE]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) ~[netty-codec-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) ~[netty-codec-4.1.31.Final.jar:4.1.31.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) ~[netty-codec-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) ~[netty-transport-4.1.31.Final.jar:4.1.31.Final]
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799) ~[netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar:4.1.31.Final]
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:433) ~[netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar:4.1.31.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:330) ~[netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar:4.1.31.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897) ~[netty-common-4.1.31.Final.jar:4.1.31.Final]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Hi ,
exchange.getResponse().getHeaders() is returning an instance of
io.netty.handler.codec.http.ReadOnlyHttpHeaders.
Contents of the set method in ReadOnlyHttpHeaders is as follows. So I
believe it should also send same exception when we use set.
@Override
public HttpHeaders set(String name, Object value) {
throw new UnsupportedOperationException("read only");
}
Also we are not relying on any routes configuration(we are using Automated
routes configured for eureka), we cannot use this fure sure.
On Thu, Jan 3, 2019 at 2:03 PM YoungJae Kim notifications@github.com
wrote:
Hi, @jobinvjohn https://github.com/jobinvjohn
you can use set
exchange.getResponse().getHeaders().set(name, value);or more recommendations way
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri: http://example.org
filters:
- SetResponseHeader=X-Response-Foo, Bar—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/spring-cloud/spring-cloud-gateway/issues/748#issuecomment-451080720,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AMnpxBJrdLbOVtuzfjmCaG-kl7-0yWnvks5u_cBggaJpZM4ZnlSS
.
java.lang.UnsupportedOperationException: null
at org.springframework.http.ReadOnlyHttpHeaders.set(ReadOnlyHttpHeaders.java:82) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at com.oito.gateway.filter.RefreshTokenGlobalFilter.lambda$2(RefreshTokenGlobalFilter.java:58) ~[main/:na]
@Gsealy Thanks for the response. Still i am facing the same issue. Please find the following code that i am using ..
@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;
}
}
@jobinvjohn can you attach a complete project as a zip? i can't reproduce it. thanks.
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();
just wanted to know anyway to avoid blocking call in above code ?
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();
});
why close?
@junneyang Refer to my reply above, it can solve the problem.
@sanxun0325 did you use .beforeCommit inside a doFinally block?
Most helpful comment
Try this