spring-cloud-gateway
spring-cloud-gateway copied to clipboard
When request is aborted, cancel event is not propagated to downstream services.
When a request is aborted by the client, spring-cloud-gateway is aware of it by Subscription.cancel()
. Adding a filter can easily comprove this behavior, where
override fun apply(config: Config?): GatewayFilter {
return GatewayFilter { exchange, chain ->
chain.filter(exchange)
.log()
.doOnCancel { log.info { "ABORTED" } }
}
}
when a request is canceled, generates
2020-06-01 11:20:03.824 INFO t=boundedElastic-7 reactor.Mono.LiftFuseable.1 - | onSubscribe([Fuseable] ScopePassingSpanSubscriber)
2020-06-01 11:20:03.824 INFO t=boundedElastic-7 reactor.Mono.LiftFuseable.1 - | request(unbounded)
2020-06-01 11:20:04.639 INFO t=reactor-http-epoll-2 p.s.g.s.filter.GatewayLogFilter - ABORTED
2020-06-01 11:20:04.640 INFO t=reactor-http-epoll-2 reactor.Mono.LiftFuseable.1 - | cancel()
But if it was routed to a downstream service, SCG does not notifies the service. I don't know if this should be default but, at least, should be configurable.
Looking at NettyRoutingFilter
, only when a response is recieved, CLIENT_RESPONSE_CONN_ATTR
is set as an exchange attribute, later used in NettyWriteResponseFilter#(cleanup)
to dispose the call using doOnCancel
event.
Version:
- spring-cloud-gatway: 2.2.1
I also came across this situation mainly with mobile browsers when they go to the background and a request is in progress..
@joaobologna are you asking for logging in doOnCancel()
?
@spencergibb no, I would like to notify the downstream service that the request was canceled. As for now, the service processing the routed request will compute it even though it was canceled by the client.
how would you do such a thing?
if the connection used on NettyRoutingFilter
was disposed, wouldn't that solve the trick?
@violetagg does this make sense?
@joaobologna If I understand the issue correct, we have the following:
client -> gateway -> target system
^
| here client drops the connection
If the client drops the connection before gateway has a chance to send a request, then gateway should not send it at all. If the client drops the connection while gateway is sending the request, then gateway should cancel that operation.
Is my understanding correct?
Currently I do not see a public API provider by Spring Framework that can be used for obtaining the connection when on the server side.
@rstoyanchev Is there a way Gateway to understand that the connection between client -> gateway
was dropped?
On the other hand if the connection is closed Reactor Netty will definitely propagate the cancellation to Spring Framework.
On the other hand if the connection is closed Reactor Netty will definitely propagate the cancellation to Spring Framework.
That's what I would expect. Isn't that what @joaobologna demonstrated in his original comment with a log message in doOnCancel
?
@violetagg that's correct.
If the client drops the connection before gateway has a chance to send a request, then gateway should not send it at all.
This scenario probably already happens, since the connection was dropped and gateway is aware of it, no further code is executed to reach a point where the request is routed.
If the client drops the connection while gateway is sending the request, then gateway should cancel that operation.
This is what is not happening and I would like to. I tried to dispose the connection used in NettyRoutingFilter
but unfortunately, it didn't work.
@rstoyanchev yes, it is.
@joaobologna So if you include the code below, it does not cancel the request?
Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);
if (connection != null) {
connection.dispose();
}
@spencergibb I think that caching the connection might be moved to this https://github.com/spring-cloud/spring-cloud-gateway/blob/cde4d564172f9eb87d8f74a221b1fa5dde42abc7/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java#L143
@violetagg CLIENT_RESPONSE_CONN_ATTR
is only available when there is a response from the server, so if connection is aborted before that, it will always be null.
I did save the connection on the exchange attributes on:
https://github.com/spring-cloud/spring-cloud-gateway/blob/cde4d564172f9eb87d8f74a221b1fa5dde42abc7/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java#L143
nettyOutbound.withConnection(connection -> exchange.getAttributes().put("conn", connection));
While configuring the Flux
response, I added an doOnCancel
event to dispose de connection:
https://github.com/spring-cloud/spring-cloud-gateway/blob/cde4d564172f9eb87d8f74a221b1fa5dde42abc7/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java#L197
...
.doOnCancel(() -> cleanup(exchange));
}
return responseFlux.then(chain.filter(exchange));
}
private void cleanup(ServerWebExchange exchange) {
Connection connection = exchange.getAttribute("conn");
if (connection != null) {
connection.dispose();
}
}
On my tests, my downstream service is still unaware of it :disappointed: I assumed since spring-cloud-gateway receives a cancel signal from the browser since the call originated from there, it could be reproduced with netty HTTP clients that are used in the project.
PS: downstream service is using webflux as well, and if the request is directly made to it (not going through gateway) it receives the cancel signal as well.
@joaobologna just to check that either you have log level trace or you invoked .withConnection
outside of the if (log.isTraceEnabled()) {
haha I made that mistake at first @violetagg, but it is outside since it was tested as mentioned in https://github.com/spring-cloud/spring-cloud-gateway/issues/1751#issuecomment-638238213
@violetagg @spencergibb just checking if you have any idea how to solve the mentioned problem.
@joaobologna Please excuse me for the delayed reply, but I made one change in Reactor Netty that might help here https://github.com/reactor/reactor-netty/commit/c61e556fed1c7d28cd7d389a4126ded0b74a51a0 Will you be able to try it? (0.9.10.BUILD-SNAPSHOT)
Hi guys, is this issue resolved? it seems that still exists, is there any workaround? I used the latest netty 1.0.7
Hi again, We tried the following code, but it seems that either doOnCancel or connection.dispose(); function. Connection seems to be always null as joaobologna allready mentioned, If there another way to achieve the following ?
@Override public GatewayFilter apply(Object config) { private void cleanup(ServerWebExchange exchange) { Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR); if (connection != null) { connection.dispose(); } } return (exchange, chain) -> {
return chain.filter(exchange).doOnCancel(() ->
{ cleanup(exchange) ;
log.info("Cancel connection");
}
);
};
}
Hi @violetagg, I am also interested in this functionality, I would like to drop the connection to my downstream APIs in the event the client drops the connection with the gateway. Any updates on this function?
:+1:
Is there any progress?