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

UnsupportedOperationException if modifyRequestBody uses LinkedMultiValueMap

Open wxiangliang opened this issue 6 years ago • 3 comments

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

wxiangliang avatar Jul 07 '18 01:07 wxiangliang

Please learn how to properly format code and logs.

spencergibb avatar Nov 19 '18 17:11 spencergibb

@qcastel willing to submit a PR based on your comments?

spencergibb avatar Nov 19 '18 17:11 spencergibb

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

NadChel avatar Mar 23 '24 15:03 NadChel