Skip to content

Commit

Permalink
Align multipart codecs on client and server
Browse files Browse the repository at this point in the history
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 spring-projectsgh-29630
  • Loading branch information
poutsma committed Dec 8, 2022
1 parent f07a458 commit ae6a2b5
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 205 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -17,7 +17,6 @@
package org.springframework.http.codec;

import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;

/**
* Extension of {@link CodecConfigurer} for HTTP message reader and writer
Expand Down Expand Up @@ -83,13 +82,6 @@ static ClientCodecConfigurer create() {
*/
interface ClientDefaultCodecs extends DefaultCodecs {

/**
* Configure encoders or writers for use with
* {@link org.springframework.http.codec.multipart.MultipartHttpMessageWriter
* MultipartHttpMessageWriter}.
*/
MultipartCodecs multipartCodecs();

/**
* Configure the {@code Decoder} to use for Server-Sent Events.
* <p>By default if this is not set, and Jackson is available, the
Expand All @@ -102,26 +94,4 @@ interface ClientDefaultCodecs extends DefaultCodecs {
void serverSentEventDecoder(Decoder<?> decoder);
}


/**
* Registry and container for multipart HTTP message writers.
*/
interface MultipartCodecs {

/**
* Add a Part {@code Encoder}, internally wrapped with
* {@link EncoderHttpMessageWriter}.
* @param encoder the encoder to add
*/
MultipartCodecs encoder(Encoder<?> encoder);

/**
* Add a Part {@link HttpMessageWriter}. For writers of type
* {@link EncoderHttpMessageWriter} consider using the shortcut
* {@link #encoder(Encoder)} instead.
* @param writer the writer to add
*/
MultipartCodecs writer(HttpMessageWriter<?> writer);
}

}
Expand Up @@ -258,6 +258,24 @@ interface DefaultCodecs {
* @since 5.1
*/
void enableLoggingRequestDetails(boolean enable);

/**
* Configure encoders or writers for use with
* {@link org.springframework.http.codec.multipart.MultipartHttpMessageWriter
* MultipartHttpMessageWriter}.
* @since 6.0.3
*/
MultipartCodecs multipartCodecs();

/**
* Configure the {@code HttpMessageReader} to use for multipart requests.
* <p>Note that {@link #maxInMemorySize(int)} and/or
* {@link #enableLoggingRequestDetails(boolean)}, if configured, will be
* applied to the given reader, if applicable.
* @param reader the message reader to use for multipart requests.
* @since 6.0.3
*/
void multipartReader(HttpMessageReader<?> reader);
}


Expand Down Expand Up @@ -389,4 +407,27 @@ interface DefaultCodecConfig {
Boolean isEnableLoggingRequestDetails();
}


/**
* Registry and container for multipart HTTP message writers.
* @since 6.0.3
*/
interface MultipartCodecs {

/**
* Add a Part {@code Encoder}, internally wrapped with
* {@link EncoderHttpMessageWriter}.
* @param encoder the encoder to add
*/
MultipartCodecs encoder(Encoder<?> encoder);

/**
* Add a Part {@link HttpMessageWriter}. For writers of type
* {@link EncoderHttpMessageWriter} consider using the shortcut
* {@link #encoder(Encoder)} instead.
* @param writer the writer to add
*/
MultipartCodecs writer(HttpMessageWriter<?> writer);
}

}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
Expand Down Expand Up @@ -82,16 +82,6 @@ static ServerCodecConfigurer create() {
*/
interface ServerDefaultCodecs extends DefaultCodecs {

/**
* Configure the {@code HttpMessageReader} to use for multipart requests.
* <p>Note that {@link #maxInMemorySize(int)} and/or
* {@link #enableLoggingRequestDetails(boolean)}, if configured, will be
* applied to the given reader, if applicable.
* @param reader the message reader to use for multipart requests.
* @since 5.1.11
*/
void multipartReader(HttpMessageReader<?> reader);

/**
* Configure the {@code Encoder} to use for Server-Sent Events.
* <p>By default if this is not set, and Jackson is available, the
Expand Down
Expand Up @@ -79,7 +79,7 @@ public class MultipartHttpMessageWriter extends MultipartWriterSupport
private static final Map<String, Object> DEFAULT_HINTS = Hints.from(Hints.SUPPRESS_LOGGING_HINT, true);


private final List<HttpMessageWriter<?>> partWriters;
private final Supplier<List<HttpMessageWriter<?>>> partWritersSupplier;

@Nullable
private final HttpMessageWriter<MultiValueMap<String, String>> formWriter;
Expand Down Expand Up @@ -112,8 +112,23 @@ public MultipartHttpMessageWriter(List<HttpMessageWriter<?>> partWriters) {
public MultipartHttpMessageWriter(List<HttpMessageWriter<?>> partWriters,
@Nullable HttpMessageWriter<MultiValueMap<String, String>> formWriter) {

this(() -> partWriters, formWriter);
}

/**
* Constructor with a supplier for an explicit list of writers for
* serializing parts and a writer for plain form data to fall back when
* no media type is specified and the actual map consists of String
* values only.
* @param partWritersSupplier the supplier for writers for serializing parts
* @param formWriter the fallback writer for form data, {@code null} by default
* @since 6.0.3
*/
public MultipartHttpMessageWriter(Supplier<List<HttpMessageWriter<?>>> partWritersSupplier,
@Nullable HttpMessageWriter<MultiValueMap<String, String>> formWriter) {

super(initMediaTypes(formWriter));
this.partWriters = partWriters;
this.partWritersSupplier = partWritersSupplier;
this.formWriter = formWriter;
}

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


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

MediaType contentType = headers.getContentType();

final ResolvableType finalBodyType = resolvableType;
Optional<HttpMessageWriter<?>> writer = this.partWriters.stream()
ResolvableType finalBodyType = resolvableType;
Optional<HttpMessageWriter<?>> writer = this.partWritersSupplier.get().stream()
.filter(partWriter -> partWriter.canWrite(finalBodyType, contentType))
.findFirst();

Expand Down
Expand Up @@ -55,6 +55,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
Assert.notNull(defaultCodecs, "'defaultCodecs' is required");
this.defaultCodecs = defaultCodecs;
this.customCodecs = new DefaultCustomCodecs();
this.defaultCodecs.setPartWritersSupplier(this::getWriters);
}

/**
Expand All @@ -64,6 +65,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
protected BaseCodecConfigurer(BaseCodecConfigurer other) {
this.defaultCodecs = other.cloneDefaultCodecs();
this.customCodecs = new DefaultCustomCodecs(other.customCodecs);
this.defaultCodecs.setPartWritersSupplier(this::getWriters);
}

/**
Expand Down
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.springframework.core.codec.AbstractDataBufferDecoder;
import org.springframework.core.codec.ByteArrayDecoder;
Expand Down Expand Up @@ -62,6 +63,8 @@
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
import org.springframework.http.codec.multipart.PartEventHttpMessageWriter;
import org.springframework.http.codec.multipart.PartHttpMessageWriter;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder;
import org.springframework.http.codec.protobuf.ProtobufDecoder;
Expand Down Expand Up @@ -160,6 +163,15 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
@Nullable
private Encoder<?> kotlinSerializationProtobufEncoder;

@Nullable
private DefaultMultipartCodecs multipartCodecs;

@Nullable
private Supplier<List<HttpMessageWriter<?>>> partWritersSupplier;

@Nullable
private HttpMessageReader<?> multipartReader;

@Nullable
private Consumer<Object> codecConsumer;

Expand Down Expand Up @@ -224,6 +236,9 @@ protected BaseDefaultCodecs(BaseDefaultCodecs other) {
this.kotlinSerializationJsonEncoder = other.kotlinSerializationJsonEncoder;
this.kotlinSerializationProtobufDecoder = other.kotlinSerializationProtobufDecoder;
this.kotlinSerializationProtobufEncoder = other.kotlinSerializationProtobufEncoder;
this.multipartCodecs = other.multipartCodecs != null ?
new DefaultMultipartCodecs(other.multipartCodecs) : null;
this.multipartReader = other.multipartReader;
this.codecConsumer = other.codecConsumer;
this.maxInMemorySize = other.maxInMemorySize;
this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
Expand Down Expand Up @@ -351,6 +366,31 @@ public void enableLoggingRequestDetails(boolean enable) {
}
}

@Override
public CodecConfigurer.MultipartCodecs multipartCodecs() {
if (this.multipartCodecs == null) {
this.multipartCodecs = new DefaultMultipartCodecs();
}
return this.multipartCodecs;
}

@Override
public void multipartReader(HttpMessageReader<?> multipartReader) {
this.multipartReader = multipartReader;
initTypedReaders();
}

/**
* Set a supplier for part writers to use when
* {@link #multipartCodecs()} are not explicitly configured.
* That's the same set of writers as for general except for the multipart
* writer itself.
*/
void setPartWritersSupplier(Supplier<List<HttpMessageWriter<?>>> supplier) {
this.partWritersSupplier = supplier;
initTypedWriters();
}

@Override
@Nullable
public Boolean isEnableLoggingRequestDetails() {
Expand Down Expand Up @@ -405,6 +445,15 @@ else if (kotlinSerializationProtobufPresent) {
(KotlinSerializationProtobufDecoder) this.kotlinSerializationProtobufDecoder : new KotlinSerializationProtobufDecoder()));
}
addCodec(this.typedReaders, new FormHttpMessageReader());
if (this.multipartReader != null) {
addCodec(this.typedReaders, this.multipartReader);
}
else {
DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();
addCodec(this.typedReaders, partReader);
addCodec(this.typedReaders, new MultipartHttpMessageReader(partReader));
}
addCodec(this.typedReaders, new PartEventHttpMessageReader());

// client vs server..
extendTypedReaders(this.typedReaders);
Expand Down Expand Up @@ -641,9 +690,25 @@ final List<HttpMessageWriter<?>> getBaseTypedWriters() {
addCodec(writers, new ProtobufHttpMessageWriter(this.protobufEncoder != null ?
(ProtobufEncoder) this.protobufEncoder : new ProtobufEncoder()));
}
addCodec(writers, new MultipartHttpMessageWriter(this::getPartWriters, new FormHttpMessageWriter()));
addCodec(writers, new PartEventHttpMessageWriter());
addCodec(writers, new PartHttpMessageWriter());
return writers;
}

private List<HttpMessageWriter<?>> getPartWriters() {
if (this.multipartCodecs != null) {
return this.multipartCodecs.getWriters();
}
else if (this.partWritersSupplier != null) {
return this.partWritersSupplier.get();
}
else {
return Collections.emptyList();
}
}


/**
* Hook for client or server specific typed writers.
*/
Expand Down Expand Up @@ -766,4 +831,41 @@ protected Encoder<?> getKotlinSerializationJsonEncoder() {
return this.kotlinSerializationJsonEncoder;
}


/**
* Default implementation of {@link CodecConfigurer.MultipartCodecs}.
*/
protected class DefaultMultipartCodecs implements CodecConfigurer.MultipartCodecs {

private final List<HttpMessageWriter<?>> writers = new ArrayList<>();


DefaultMultipartCodecs() {
}

DefaultMultipartCodecs(DefaultMultipartCodecs other) {
this.writers.addAll(other.writers);
}


@Override
public CodecConfigurer.MultipartCodecs encoder(Encoder<?> encoder) {
writer(new EncoderHttpMessageWriter<>(encoder));
initTypedWriters();
return this;
}

@Override
public CodecConfigurer.MultipartCodecs writer(HttpMessageWriter<?> writer) {
this.writers.add(writer);
initTypedWriters();
return this;
}

List<HttpMessageWriter<?>> getWriters() {
return this.writers;
}
}


}

0 comments on commit ae6a2b5

Please sign in to comment.