spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.52k stars 3.32k forks source link

When a websocket request is initiated, the response header type is ReadOnlyHttpHeaders #2258

Open bojiangzhou opened 3 years ago

bojiangzhou commented 3 years ago

Dependent version

1. application.yml

Add one of SetResponseHeader or DedupeResponseHeader under "default-filters"

spring:
  cloud:
    gateway:
      default-filters:
        - SetResponseHeader=Access-Control-Expose-Headers, Content-Disposition
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin Vary, RETAIN_UNIQUE

2. a websocket request

ws://localhost:8080/hpfm/websocket?access_token=0d261199-b39e-4424-82f9-9055b92119fd

3. exception

2021-06-04 14:41:48.161 ERROR 20152 --- [ctor-http-nio-7] o.s.w.s.adapter.HttpWebHandlerAdapter    : [ee2db0e7-1] Error [java.lang.UnsupportedOperationException] for HTTP GET "/hpfm/websocket?access_token=0d261199-b39e-4424-82f9-9055b92119fd", but ServerHttpResponse already committed (200 OK)
2021-06-04 14:41:48.164 ERROR 20152 --- [ctor-http-nio-7] r.n.http.server.HttpServerOperations     : [id:ee2db0e7-1, L:/10.215.0.89:8080 - R:bojiangzhou/10.215.0.89:53965] Error finishing response. Closing connection

java.lang.UnsupportedOperationException: null
    at org.springframework.http.ReadOnlyHttpHeaders.put(ReadOnlyHttpHeaders.java:126) ~[spring-web-5.3.7.jar:5.3.7]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ org.springframework.boot.actuate.web.trace.reactive.HttpTraceWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.hzero.gateway.filter.GateWayHelperFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.web.cors.reactive.CorsWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.hzero.gateway.filter.IpCheckedFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ HTTP GET "/hpfm/websocket?access_token=0d261199-b39e-4424-82f9-9055b92119fd" [ExceptionHandlingWebHandler]
Stack trace:
        at org.springframework.http.ReadOnlyHttpHeaders.put(ReadOnlyHttpHeaders.java:126) ~[spring-web-5.3.7.jar:5.3.7]
        at org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.dedupe(DedupeResponseHeaderGatewayFilterFactory.java:147) ~[spring-cloud-gateway-server-3.0.3.jar:3.0.3]
        at org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.dedupe(DedupeResponseHeaderGatewayFilterFactory.java:130) ~[spring-cloud-gateway-server-3.0.3.jar:3.0.3]
        at org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory$1.lambda$filter$0(DedupeResponseHeaderGatewayFilterFactory.java:93) ~[spring-cloud-gateway-server-3.0.3.jar:3.0.3]
        at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:73) ~[reactor-core-3.4.6.jar:3.4.6]
        at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:32) ~[reactor-core-3.4.6.jar:3.4.6]
        at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:224) [reactor-core-3.4.6.jar:3.4.6]
..............

4. the reason

When the websocket request is completed, the request will be completed ahead of time.

image

Then the status of ServerHttpResponse will be changed to COMMITTED.

image

When finally entering DedupeResponseHeaderGatewayFilterFactory to get HttpHeaders, since the status of ServerHttpResponse is COMMITTED, it returns ReadOnlyHttpHeaders.

image image

Is this a bug, or how to solve it ?

fengzheng0571 commented 3 years ago

I have the same problem

ChampionRoy commented 3 years ago

same problem here

spencergibb commented 3 years ago

There's nothing at that point that the DedupeResponseHeaderGatewayFilterFactory or any that modify response headers can do. Maybe log a warning and move on?

MonDeveloper commented 2 years ago

Actually I do not understand your point @spencergibb, the question seems reasonable to me. I had the same problem and I solved putting the responseHeaders' update within the exchange.getResponse().beforeCommit(...); instead of the classic chain.filter(exchange).then(...);

I'm not sure I haven't broken any "very well hidden secret rule of reactor usage in spring" but it works.

Could you please tell me if I done it right?