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

java.lang.UnsupportedOperationException: null For SPring cloud gateway 4.2.0-M1 Version #3550

Closed anakhaaniejose closed 5 days ago

anakhaaniejose commented 2 weeks ago

Describe the bug I have a spring cloud gateway application that routes a request from port 8080 to s service at 8081. I have to try adding a header along with the request. So in postman I have added the headers "x-test" with value "foo" along with the request. This works perfectly fine till spring cloud gateway version 4.1.5. But when I change it to 4.2.0-M1, I'm getting this unsupportedoperation exception as given below:


java.lang.UnsupportedOperationException: null
    at java.base/java.util.AbstractList.add(AbstractList.java:153) ~[na:na]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ HTTP GET "/employee/message" [ExceptionHandlingWebHandler]
Original Stack Trace:
        at java.base/java.util.AbstractList.add(AbstractList.java:153) ~[na:na]
        at java.base/java.util.AbstractList.add(AbstractList.java:111) ~[na:na]
        at org.springframework.util.MultiValueMapAdapter.add(MultiValueMapAdapter.java:68) ~[spring-core-6.2.0-M6.jar:6.2.0-M6]
        at org.springframework.http.HttpHeaders.add(HttpHeaders.java:1754) ~[spring-web-6.2.0-M6.jar:6.2.0-M6]
        at org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory$1.lambda$filter$0(AddRequestHeaderGatewayFilterFactory.java:42) ~[spring-cloud-gateway-server-4.2.0-M1.jar:4.2.0-M1]
        at org.springframework.http.server.reactive.DefaultServerHttpRequestBuilder.headers(DefaultServerHttpRequestBuilder.java:119) ~[spring-web-6.2.0-M6.jar:6.2.0-M6]
        at org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory$1.filter(AddRequestHeaderGatewayFilterFactory.java:42) ~[spring-cloud-gateway-server-4.2.0-M1.jar:4.2.0-M1]
        at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44) ~[spring-cloud-gateway-server-4.2.0-M1.jar:4.2.0-M1]
        at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:127) ~[spring-cloud-gateway-server-4.2.0-M1.jar:4.2.0-M1]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:45) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4568) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4568) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoFilterWhen$MonoFilterWhenMain.onNext(MonoFilterWhen.java:136) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2571) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoFilterWhen$MonoFilterWhenMain.request(MonoFilterWhen.java:183) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2331) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2331) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:339) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2331) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2331) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:339) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2367) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2241) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4568) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[reactor-core-3.7.0-M4.jar:3.7.0-M4]
        at reactor.netty.http.server.HttpServer$HttpServerHandle.onStateChange(HttpServer.java:1211) ~[reactor-netty-http-1.2.0-M4.jar:1.2.0-M4]
        at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:715) ~[reactor-netty-core-1.2.0-M4.jar:1.2.0-M4]
        at reactor.netty.transport.ServerTransport$ChildObserver.onStateChange(ServerTransport.java:486) ~[reactor-netty-core-1.2.0-M4.jar:1.2.0-M4]
        at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:781) ~[reactor-netty-http-1.2.0-M4.jar:1.2.0-M4]
        at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115) ~[reactor-netty-core-1.2.0-M4.jar:1.2.0-M4]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:260) ~[reactor-netty-http-1.2.0-M4.jar:1.2.0-M4]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]

I found that this issue is occuring only when I try to add a header. I tried comparing the calss HttpHeaders and few others with previous versions and current version and could bot find substantial changes.

Sample

Here is the code I used:

Class SpringCloudConfig


import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringCloudConfig {

    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/employee/**")
                        .filters(f -> {
                            return f.addRequestHeader("x-test", "bar");
                        }) //

                        .uri("http://localhost:8081/"))
                .build();

    }

}

APIGatewayApplication


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class APIGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(APIGatewayApplication.class, args);
    }

}

Application.properties server.port=8080

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringCloudGateway</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot Dependency Management -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.4.0-M1</version>

                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>4.2.0-M1</version>

        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

this is the controller that provides the response for endpoint http://localhost:8081/employee/message


import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/employee")
public class FIrstController {

    @GetMapping("/message")
    public String test(@RequestHeader Map<String, String> headers) {
        System.out.println("######################################################################");
        headers.forEach((key, value) -> {
            System.out.println("Header: " + key + " Value: " + value);
        });
        System.out.println("######################################################################");
        return "Hello JavaInUse Called in First Service";
    }

Can someone please take a look at this issue?

anakhaaniejose commented 6 days ago

On further Analysis I found out that adding the headers "x-test" will cause error only when this code is added .filters(f -> { return f.addRequestHeader("x-test", "bar"); })

So this must be an issue with Immutability of headers in the new version. Can someone take a look?

spencergibb commented 6 days ago

I'm unable to recreate the problem given the snippets above.

If you'd like us to spend some time investigating, please take the time to provide a complete, minimal, verifiable sample (something that we can unzip attached to this issue or git clone, build, and deploy) that reproduces the problem.

anakhaaniejose commented 6 days ago

@spencergibb I'm providing the GitHub links to the applications I have used to reproduce the issue:

Please follow the following steps:

  1. clone https://github.com/anakhaaniejose/SpringCloudService

  2. Start this application first, it will be started on 8081

  3. Clone https://github.com/anakhaaniejose/SpringCloudGateway

  4. Start this application, it will be started in 8082. I have coded it in such a way that endpoints will be called directly.

  5. Starting the SpringCloudGateway can show the error I described earlier.

spencergibb commented 5 days ago

Interesting, going to httpbin.org there is no error, going to localhost there is. Actually, it was java 17 has the error, not java 21

spencergibb commented 5 days ago

Fortunately, upgrading to the latest spring boot snapshot fixes it because of https://github.com/spring-projects/spring-framework/issues/33666

anakhaaniejose commented 5 days ago

@spencergibb Does this mean with spring-cloud gateway 4.2.0 and spring boot version 3.4.0 releases, this issue will be resolved?

spencergibb commented 4 days ago

Yes