spring-cloud-gateway
spring-cloud-gateway copied to clipboard
UnsupportedOperationException if modifyRequestBody uses LinkedMultiValueMap
.route("user_route",r->r.path("/user")
.filters(
f->f.setRequestHeader("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.modifyRequestBody(MultiValueMap.class,MultiValueMap.class,((serverWebExchange, user) -> {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "John");
formData.add("nickName", "qwerty");
return Mono.just(formData);
}))
)
.uri("lb://smweb"))
java.lang.UnsupportedOperationException: null
at java.util.Collections$UnmodifiableMap.put(Collections.java:1457) ~[na:1.8.0_144]
at org.springframework.http.HttpHeaders.set(HttpHeaders.java:1451) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.http.HttpHeaders.setContentType(HttpHeaders.java:854) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.http.codec.FormHttpMessageWriter.write(FormHttpMessageWriter.java:132) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at org.springframework.http.codec.multipart.MultipartHttpMessageWriter.lambda$write$1(MultipartHttpMessageWriter.java:188) ~[spring-web-5.0.7.RELEASE.jar:5.0.7.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1083) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:115) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:115) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onNext(FluxFilterFuseable.java:104) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1083) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:117) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:130) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:245) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:130) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.ipc.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:377) ~[reactor-netty-0.7.8.RELEASE.jar:0.7.8.RELEASE]
at reactor.ipc.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:202) ~[reactor-netty-0.7.8.RELEASE.jar:0.7.8.RELEASE]
at reactor.ipc.netty.channel.FluxReceive.request(FluxReceive.java:110) ~[reactor-netty-0.7.8.RELEASE.jar:0.7.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:149) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:130) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:149) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onSubscribe(MonoCollectList.java:90) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:86) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onSubscribe(FluxPeek.java:163) ~[reactor-core-3.1.8.RELEASE.jar:3.1.8.RELEASE]
Please learn how to properly format code and logs.
@qcastel willing to submit a PR based on your comments?
From the stacktrace it's evident that your HttpHeaders
are wrapping an unmodifiable map implementation, and it most likely has nothing to do with modifyRequestBody()
. Could you provide a minimal reproducible example? Here's a little test I wrote that doesn't trigger any exception
package com.example.gatewaydemo.routeLocator;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
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;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class RouteLocatorTest {
@LocalServerPort
int port;
@Test
public void test() {
Mono<ResponseEntity<Void>> responseEntityMono = WebClient.builder()
.baseUrl("http://localhost:" + port)
.build()
.post()
.uri("/api/user")
.body(Mono.just(body), String.class)
.retrieve()
.toBodilessEntity();
StepVerifier.create(responseEntityMono)
.expectNextMatches(r -> r.getStatusCode().is2xxSuccessful())
.verifyComplete();
}
@Configuration
@EnableAutoConfiguration
static class RouteLocatorTestConfig {
@Bean
RouterFunction<ServerResponse> postRouterFunction() {
return RouterFunctions.route()
.POST("/user", request -> request.bodyToMono(MultiValueMap.class)
.flatMap(body -> {
assertThat(body.get("username")).asList().containsExactly("John");
assertThat(body.get("password")).asList().containsExactly("qwerty");
return ServerResponse.ok().build();
})
.onErrorResume(AssertionError.class, t -> ServerResponse.badRequest().build()))
.build();
}
@Bean
RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/api/user")
.filters(f -> f.rewritePath("/api", "")
.setRequestHeader(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.modifyRequestBody(MultiValueMap.class,
MultiValueMap.class,
(serverWebExchange, body) -> {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "John");
formData.add("password", "qwerty");
return Mono.just(formData);
}))
.uri("http://localhost:8080"))
.build();
}
}
}