Skip to content

Commit c79ae0c

Browse files
committed
Align multipart codecs on client and server
This commit ensures that the same multipart codecs are registered on both client and server. Previously, only the client enabled only sending multipart, and the server only receiving. Closes gh-29630
1 parent 46fc28f commit c79ae0c

File tree

13 files changed

+216
-205
lines changed

13 files changed

+216
-205
lines changed

spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
1717
package org.springframework.http.codec;
1818

1919
import org.springframework.core.codec.Decoder;
20-
import org.springframework.core.codec.Encoder;
2120

2221
/**
2322
* Extension of {@link CodecConfigurer} for HTTP message reader and writer
@@ -83,13 +82,6 @@ static ClientCodecConfigurer create() {
8382
*/
8483
interface ClientDefaultCodecs extends DefaultCodecs {
8584

86-
/**
87-
* Configure encoders or writers for use with
88-
* {@link org.springframework.http.codec.multipart.MultipartHttpMessageWriter
89-
* MultipartHttpMessageWriter}.
90-
*/
91-
MultipartCodecs multipartCodecs();
92-
9385
/**
9486
* Configure the {@code Decoder} to use for Server-Sent Events.
9587
* <p>By default if this is not set, and Jackson is available, the
@@ -102,26 +94,4 @@ interface ClientDefaultCodecs extends DefaultCodecs {
10294
void serverSentEventDecoder(Decoder<?> decoder);
10395
}
10496

105-
106-
/**
107-
* Registry and container for multipart HTTP message writers.
108-
*/
109-
interface MultipartCodecs {
110-
111-
/**
112-
* Add a Part {@code Encoder}, internally wrapped with
113-
* {@link EncoderHttpMessageWriter}.
114-
* @param encoder the encoder to add
115-
*/
116-
MultipartCodecs encoder(Encoder<?> encoder);
117-
118-
/**
119-
* Add a Part {@link HttpMessageWriter}. For writers of type
120-
* {@link EncoderHttpMessageWriter} consider using the shortcut
121-
* {@link #encoder(Encoder)} instead.
122-
* @param writer the writer to add
123-
*/
124-
MultipartCodecs writer(HttpMessageWriter<?> writer);
125-
}
126-
12797
}

spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,24 @@ interface DefaultCodecs {
258258
* @since 5.1
259259
*/
260260
void enableLoggingRequestDetails(boolean enable);
261+
262+
/**
263+
* Configure encoders or writers for use with
264+
* {@link org.springframework.http.codec.multipart.MultipartHttpMessageWriter
265+
* MultipartHttpMessageWriter}.
266+
* @since 6.0.3
267+
*/
268+
MultipartCodecs multipartCodecs();
269+
270+
/**
271+
* Configure the {@code HttpMessageReader} to use for multipart requests.
272+
* <p>Note that {@link #maxInMemorySize(int)} and/or
273+
* {@link #enableLoggingRequestDetails(boolean)}, if configured, will be
274+
* applied to the given reader, if applicable.
275+
* @param reader the message reader to use for multipart requests.
276+
* @since 6.0.3
277+
*/
278+
void multipartReader(HttpMessageReader<?> reader);
261279
}
262280

263281

@@ -389,4 +407,27 @@ interface DefaultCodecConfig {
389407
Boolean isEnableLoggingRequestDetails();
390408
}
391409

410+
411+
/**
412+
* Registry and container for multipart HTTP message writers.
413+
* @since 6.0.3
414+
*/
415+
interface MultipartCodecs {
416+
417+
/**
418+
* Add a Part {@code Encoder}, internally wrapped with
419+
* {@link EncoderHttpMessageWriter}.
420+
* @param encoder the encoder to add
421+
*/
422+
MultipartCodecs encoder(Encoder<?> encoder);
423+
424+
/**
425+
* Add a Part {@link HttpMessageWriter}. For writers of type
426+
* {@link EncoderHttpMessageWriter} consider using the shortcut
427+
* {@link #encoder(Encoder)} instead.
428+
* @param writer the writer to add
429+
*/
430+
MultipartCodecs writer(HttpMessageWriter<?> writer);
431+
}
432+
392433
}

spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -82,16 +82,6 @@ static ServerCodecConfigurer create() {
8282
*/
8383
interface ServerDefaultCodecs extends DefaultCodecs {
8484

85-
/**
86-
* Configure the {@code HttpMessageReader} to use for multipart requests.
87-
* <p>Note that {@link #maxInMemorySize(int)} and/or
88-
* {@link #enableLoggingRequestDetails(boolean)}, if configured, will be
89-
* applied to the given reader, if applicable.
90-
* @param reader the message reader to use for multipart requests.
91-
* @since 5.1.11
92-
*/
93-
void multipartReader(HttpMessageReader<?> reader);
94-
9585
/**
9686
* Configure the {@code Encoder} to use for Server-Sent Events.
9787
* <p>By default if this is not set, and Jackson is available, the

spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public class MultipartHttpMessageWriter extends MultipartWriterSupport
7979
private static final Map<String, Object> DEFAULT_HINTS = Hints.from(Hints.SUPPRESS_LOGGING_HINT, true);
8080

8181

82-
private final List<HttpMessageWriter<?>> partWriters;
82+
private final Supplier<List<HttpMessageWriter<?>>> partWritersSupplier;
8383

8484
@Nullable
8585
private final HttpMessageWriter<MultiValueMap<String, String>> formWriter;
@@ -112,8 +112,23 @@ public MultipartHttpMessageWriter(List<HttpMessageWriter<?>> partWriters) {
112112
public MultipartHttpMessageWriter(List<HttpMessageWriter<?>> partWriters,
113113
@Nullable HttpMessageWriter<MultiValueMap<String, String>> formWriter) {
114114

115+
this(() -> partWriters, formWriter);
116+
}
117+
118+
/**
119+
* Constructor with a supplier for an explicit list of writers for
120+
* serializing parts and a writer for plain form data to fall back when
121+
* no media type is specified and the actual map consists of String
122+
* values only.
123+
* @param partWritersSupplier the supplier for writers for serializing parts
124+
* @param formWriter the fallback writer for form data, {@code null} by default
125+
* @since 6.0.3
126+
*/
127+
public MultipartHttpMessageWriter(Supplier<List<HttpMessageWriter<?>>> partWritersSupplier,
128+
@Nullable HttpMessageWriter<MultiValueMap<String, String>> formWriter) {
129+
115130
super(initMediaTypes(formWriter));
116-
this.partWriters = partWriters;
131+
this.partWritersSupplier = partWritersSupplier;
117132
this.formWriter = formWriter;
118133
}
119134

@@ -131,7 +146,7 @@ private static List<MediaType> initMediaTypes(@Nullable HttpMessageWriter<?> for
131146
* @since 5.0.7
132147
*/
133148
public List<HttpMessageWriter<?>> getPartWriters() {
134-
return Collections.unmodifiableList(this.partWriters);
149+
return Collections.unmodifiableList(this.partWritersSupplier.get());
135150
}
136151

137152

@@ -264,8 +279,8 @@ else if (resolvableType.resolve() == Resource.class) {
264279

265280
MediaType contentType = headers.getContentType();
266281

267-
final ResolvableType finalBodyType = resolvableType;
268-
Optional<HttpMessageWriter<?>> writer = this.partWriters.stream()
282+
ResolvableType finalBodyType = resolvableType;
283+
Optional<HttpMessageWriter<?>> writer = this.partWritersSupplier.get().stream()
269284
.filter(partWriter -> partWriter.canWrite(finalBodyType, contentType))
270285
.findFirst();
271286

spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
5555
Assert.notNull(defaultCodecs, "'defaultCodecs' is required");
5656
this.defaultCodecs = defaultCodecs;
5757
this.customCodecs = new DefaultCustomCodecs();
58+
this.defaultCodecs.setPartWritersSupplier(this::getWriters);
5859
}
5960

6061
/**
@@ -64,6 +65,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
6465
protected BaseCodecConfigurer(BaseCodecConfigurer other) {
6566
this.defaultCodecs = other.cloneDefaultCodecs();
6667
this.customCodecs = new DefaultCustomCodecs(other.customCodecs);
68+
this.defaultCodecs.setPartWritersSupplier(this::getWriters);
6769
}
6870

6971
/**

spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.function.Consumer;
24+
import java.util.function.Supplier;
2425

2526
import org.springframework.core.codec.AbstractDataBufferDecoder;
2627
import org.springframework.core.codec.ByteArrayDecoder;
@@ -62,6 +63,8 @@
6263
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
6364
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
6465
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
66+
import org.springframework.http.codec.multipart.PartEventHttpMessageWriter;
67+
import org.springframework.http.codec.multipart.PartHttpMessageWriter;
6568
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder;
6669
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder;
6770
import org.springframework.http.codec.protobuf.ProtobufDecoder;
@@ -160,6 +163,15 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
160163
@Nullable
161164
private Encoder<?> kotlinSerializationProtobufEncoder;
162165

166+
@Nullable
167+
private DefaultMultipartCodecs multipartCodecs;
168+
169+
@Nullable
170+
private Supplier<List<HttpMessageWriter<?>>> partWritersSupplier;
171+
172+
@Nullable
173+
private HttpMessageReader<?> multipartReader;
174+
163175
@Nullable
164176
private Consumer<Object> codecConsumer;
165177

@@ -224,6 +236,9 @@ protected BaseDefaultCodecs(BaseDefaultCodecs other) {
224236
this.kotlinSerializationJsonEncoder = other.kotlinSerializationJsonEncoder;
225237
this.kotlinSerializationProtobufDecoder = other.kotlinSerializationProtobufDecoder;
226238
this.kotlinSerializationProtobufEncoder = other.kotlinSerializationProtobufEncoder;
239+
this.multipartCodecs = other.multipartCodecs != null ?
240+
new DefaultMultipartCodecs(other.multipartCodecs) : null;
241+
this.multipartReader = other.multipartReader;
227242
this.codecConsumer = other.codecConsumer;
228243
this.maxInMemorySize = other.maxInMemorySize;
229244
this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
@@ -351,6 +366,31 @@ public void enableLoggingRequestDetails(boolean enable) {
351366
}
352367
}
353368

369+
@Override
370+
public CodecConfigurer.MultipartCodecs multipartCodecs() {
371+
if (this.multipartCodecs == null) {
372+
this.multipartCodecs = new DefaultMultipartCodecs();
373+
}
374+
return this.multipartCodecs;
375+
}
376+
377+
@Override
378+
public void multipartReader(HttpMessageReader<?> multipartReader) {
379+
this.multipartReader = multipartReader;
380+
initTypedReaders();
381+
}
382+
383+
/**
384+
* Set a supplier for part writers to use when
385+
* {@link #multipartCodecs()} are not explicitly configured.
386+
* That's the same set of writers as for general except for the multipart
387+
* writer itself.
388+
*/
389+
void setPartWritersSupplier(Supplier<List<HttpMessageWriter<?>>> supplier) {
390+
this.partWritersSupplier = supplier;
391+
initTypedWriters();
392+
}
393+
354394
@Override
355395
@Nullable
356396
public Boolean isEnableLoggingRequestDetails() {
@@ -405,6 +445,15 @@ else if (kotlinSerializationProtobufPresent) {
405445
(KotlinSerializationProtobufDecoder) this.kotlinSerializationProtobufDecoder : new KotlinSerializationProtobufDecoder()));
406446
}
407447
addCodec(this.typedReaders, new FormHttpMessageReader());
448+
if (this.multipartReader != null) {
449+
addCodec(this.typedReaders, this.multipartReader);
450+
}
451+
else {
452+
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
453+
addCodec(this.typedReaders, partReader);
454+
addCodec(this.typedReaders, new MultipartHttpMessageReader(partReader));
455+
}
456+
addCodec(this.typedReaders, new PartEventHttpMessageReader());
408457

409458
// client vs server..
410459
extendTypedReaders(this.typedReaders);
@@ -641,9 +690,25 @@ final List<HttpMessageWriter<?>> getBaseTypedWriters() {
641690
addCodec(writers, new ProtobufHttpMessageWriter(this.protobufEncoder != null ?
642691
(ProtobufEncoder) this.protobufEncoder : new ProtobufEncoder()));
643692
}
693+
addCodec(writers, new MultipartHttpMessageWriter(this::getPartWriters, new FormHttpMessageWriter()));
694+
addCodec(writers, new PartEventHttpMessageWriter());
695+
addCodec(writers, new PartHttpMessageWriter());
644696
return writers;
645697
}
646698

699+
private List<HttpMessageWriter<?>> getPartWriters() {
700+
if (this.multipartCodecs != null) {
701+
return this.multipartCodecs.getWriters();
702+
}
703+
else if (this.partWritersSupplier != null) {
704+
return this.partWritersSupplier.get();
705+
}
706+
else {
707+
return Collections.emptyList();
708+
}
709+
}
710+
711+
647712
/**
648713
* Hook for client or server specific typed writers.
649714
*/
@@ -766,4 +831,41 @@ protected Encoder<?> getKotlinSerializationJsonEncoder() {
766831
return this.kotlinSerializationJsonEncoder;
767832
}
768833

834+
835+
/**
836+
* Default implementation of {@link CodecConfigurer.MultipartCodecs}.
837+
*/
838+
protected class DefaultMultipartCodecs implements CodecConfigurer.MultipartCodecs {
839+
840+
private final List<HttpMessageWriter<?>> writers = new ArrayList<>();
841+
842+
843+
DefaultMultipartCodecs() {
844+
}
845+
846+
DefaultMultipartCodecs(DefaultMultipartCodecs other) {
847+
this.writers.addAll(other.writers);
848+
}
849+
850+
851+
@Override
852+
public CodecConfigurer.MultipartCodecs encoder(Encoder<?> encoder) {
853+
writer(new EncoderHttpMessageWriter<>(encoder));
854+
initTypedWriters();
855+
return this;
856+
}
857+
858+
@Override
859+
public CodecConfigurer.MultipartCodecs writer(HttpMessageWriter<?> writer) {
860+
this.writers.add(writer);
861+
initTypedWriters();
862+
return this;
863+
}
864+
865+
List<HttpMessageWriter<?>> getWriters() {
866+
return this.writers;
867+
}
868+
}
869+
870+
769871
}

0 commit comments

Comments
 (0)