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