spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

When request is aborted, cancel event is not propagated to downstream services.

Open joaobologna opened this issue 4 years ago • 20 comments

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

joaobologna avatar Jun 01 '20 14:06 joaobologna

I also came across this situation mainly with mobile browsers when they go to the background and a request is in progress..

jovan-fernandes avatar Jun 01 '20 14:06 jovan-fernandes

@joaobologna are you asking for logging in doOnCancel()?

spencergibb avatar Jun 01 '20 14:06 spencergibb

@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.

joaobologna avatar Jun 01 '20 15:06 joaobologna

how would you do such a thing?

spencergibb avatar Jun 01 '20 15:06 spencergibb

if the connection used on NettyRoutingFilter was disposed, wouldn't that solve the trick?

joaobologna avatar Jun 01 '20 15:06 joaobologna

@violetagg does this make sense?

spencergibb avatar Jun 01 '20 15:06 spencergibb

@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.

violetagg avatar Jun 02 '20 18:06 violetagg

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?

rstoyanchev avatar Jun 03 '20 09:06 rstoyanchev

@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 avatar Jun 03 '20 14:06 joaobologna

@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 avatar Jun 03 '20 16:06 violetagg

@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 avatar Jun 03 '20 17:06 joaobologna

@joaobologna just to check that either you have log level trace or you invoked .withConnection outside of the if (log.isTraceEnabled()) {

violetagg avatar Jun 03 '20 18:06 violetagg

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

joaobologna avatar Jun 03 '20 18:06 joaobologna

@violetagg @spencergibb just checking if you have any idea how to solve the mentioned problem.

joaobologna avatar Jun 15 '20 20:06 joaobologna

@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)

violetagg avatar Jul 09 '20 16:07 violetagg

Hi guys, is this issue resolved? it seems that still exists, is there any workaround? I used the latest netty 1.0.7

scharalabos avatar Jun 04 '21 10:06 scharalabos

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");
                }
        );
    };
}

scharalabos avatar Jun 14 '21 13:06 scharalabos

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?

Zarozz avatar Dec 15 '23 00:12 Zarozz

:+1:

heowc avatar Apr 04 '24 07:04 heowc

Is there any progress?

cobolbaby avatar Sep 01 '24 03:09 cobolbaby