From 3f8a10c19ff17db38f02f909547783cceb56fb8c Mon Sep 17 00:00:00 2001 From: Sergey Zolotaryov Date: Thu, 11 Apr 2024 18:20:30 +0300 Subject: [PATCH 1/2] Remove Content-Type when body is empty Closes gh-32622 See gh-32620 --- .../http/codec/EncoderHttpMessageWriter.java | 13 +++-- .../codec/EncoderHttpMessageWriterTests.java | 58 +++++++++++++++---- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 239ddf0a69f9..82896bb24077 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -16,20 +16,15 @@ package org.springframework.http.codec; -import java.util.List; -import java.util.Map; - import org.apache.commons.logging.Log; import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - import org.springframework.core.ResolvableType; import org.springframework.core.codec.AbstractEncoder; import org.springframework.core.codec.Encoder; import org.springframework.core.codec.Hints; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpLogging; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; @@ -38,6 +33,11 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; /** * {@code HttpMessageWriter} that wraps and delegates to an {@link Encoder}. @@ -127,6 +127,7 @@ public Mono write(Publisher inputStream, ResolvableType eleme return body .singleOrEmpty() .switchIfEmpty(Mono.defer(() -> { + message.getHeaders().remove(HttpHeaders.CONTENT_TYPE); message.getHeaders().setContentLength(0); return message.setComplete().then(Mono.empty()); })) diff --git a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java index eff738c3c9f0..c1b65fc465fa 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java @@ -16,31 +16,36 @@ package org.springframework.http.codec; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - +import org.springframework.core.ResolvableType; import org.springframework.core.codec.CharSequenceEncoder; +import org.springframework.core.codec.Encoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.web.testfixture.http.client.reactive.MockClientHttpRequest; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; @@ -199,6 +204,35 @@ void isStreamingMediaType() throws InvocationTargetException, IllegalAccessExcep assertThat((Boolean) method.invoke(writer, TEXT_HTML)).isFalse(); } + @Test + public void ifBodyPublisherEmpty_noContentTypeHeader() { + Encoder encoder = CharSequenceEncoder.textPlainOnly(); + EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); + ReactiveHttpOutputMessage outputMessage = new MockClientHttpRequest(HttpMethod.POST, "/"); + Mono writerMono = writer.write(Mono.empty(), ResolvableType.forClass(String.class), + null, outputMessage, NO_HINTS); + + StepVerifier.create(writerMono) + .verifyComplete(); + assertThat(outputMessage.getHeaders()).doesNotContainKey(HttpHeaders.CONTENT_TYPE); + } + + @Test + public void ifBodyPublisherEmpty_contentLengthZero() { + Encoder encoder = CharSequenceEncoder.textPlainOnly(); + EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); + ReactiveHttpOutputMessage outputMessage = new MockClientHttpRequest(HttpMethod.POST, "/"); + Mono writerMono = writer.write(Mono.empty(), ResolvableType.forClass(String.class), + null, outputMessage, NO_HINTS); + + StepVerifier.create(writerMono) + .verifyComplete(); + List contentLengthValues = outputMessage.getHeaders().get(HttpHeaders.CONTENT_LENGTH); + assertThat(contentLengthValues).hasSize(1); + int contentLength = Integer.parseInt(contentLengthValues.get(0)); + assertThat(contentLength).isEqualTo(0); + } + private void configureEncoder(MimeType... mimeTypes) { configureEncoder(Flux.empty(), mimeTypes); } From 6c5ef9715bd1670ab081320996000b29eb49b865 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 23 Apr 2024 14:23:35 +0200 Subject: [PATCH 2/2] Polishing external contribution See gh-32622 --- .../http/codec/EncoderHttpMessageWriter.java | 16 +++---- .../codec/EncoderHttpMessageWriterTests.java | 48 ++++++++----------- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 82896bb24077..fc791caab737 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,20 @@ package org.springframework.http.codec; +import java.util.List; +import java.util.Map; + import org.apache.commons.logging.Log; import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + import org.springframework.core.ResolvableType; import org.springframework.core.codec.AbstractEncoder; import org.springframework.core.codec.Encoder; import org.springframework.core.codec.Hints; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpLogging; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; @@ -33,11 +38,6 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.List; -import java.util.Map; /** * {@code HttpMessageWriter} that wraps and delegates to an {@link Encoder}. @@ -127,7 +127,7 @@ public Mono write(Publisher inputStream, ResolvableType eleme return body .singleOrEmpty() .switchIfEmpty(Mono.defer(() -> { - message.getHeaders().remove(HttpHeaders.CONTENT_TYPE); + message.getHeaders().setContentType(null); message.getHeaders().setContentLength(0); return message.setComplete().then(Mono.empty()); })) diff --git a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java index c1b65fc465fa..58479195e16f 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java @@ -16,36 +16,33 @@ package org.springframework.http.codec; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + import org.springframework.core.ResolvableType; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.Encoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.web.testfixture.http.client.reactive.MockClientHttpRequest; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; @@ -205,32 +202,27 @@ void isStreamingMediaType() throws InvocationTargetException, IllegalAccessExcep } @Test - public void ifBodyPublisherEmpty_noContentTypeHeader() { + public void noContentTypeWithEmptyBody() { Encoder encoder = CharSequenceEncoder.textPlainOnly(); - EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); - ReactiveHttpOutputMessage outputMessage = new MockClientHttpRequest(HttpMethod.POST, "/"); + HttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); Mono writerMono = writer.write(Mono.empty(), ResolvableType.forClass(String.class), - null, outputMessage, NO_HINTS); + null, this.response, NO_HINTS); StepVerifier.create(writerMono) .verifyComplete(); - assertThat(outputMessage.getHeaders()).doesNotContainKey(HttpHeaders.CONTENT_TYPE); + assertThat(response.getHeaders().getContentType()).isNull(); } @Test - public void ifBodyPublisherEmpty_contentLengthZero() { + public void zeroContentLengthWithEmptyBody() { Encoder encoder = CharSequenceEncoder.textPlainOnly(); - EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); - ReactiveHttpOutputMessage outputMessage = new MockClientHttpRequest(HttpMethod.POST, "/"); + HttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); Mono writerMono = writer.write(Mono.empty(), ResolvableType.forClass(String.class), - null, outputMessage, NO_HINTS); + null, this.response, NO_HINTS); StepVerifier.create(writerMono) .verifyComplete(); - List contentLengthValues = outputMessage.getHeaders().get(HttpHeaders.CONTENT_LENGTH); - assertThat(contentLengthValues).hasSize(1); - int contentLength = Integer.parseInt(contentLengthValues.get(0)); - assertThat(contentLength).isEqualTo(0); + assertThat(this.response.getHeaders().getContentLength()).isEqualTo(0); } private void configureEncoder(MimeType... mimeTypes) {