MIME types by Class for Encoder, Decoder, HttpMessageReader|Writer

Closes gh-26212
This commit is contained in:
Rossen Stoyanchev
2021-02-04 15:25:43 +00:00
parent 7cdaaa22bd
commit 0d16c9100a
14 changed files with 208 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -196,7 +196,7 @@ public abstract class BodyExtractors {
.map(readerFunction)
.orElseGet(() -> {
List<MediaType> mediaTypes = context.messageReaders().stream()
.flatMap(reader -> reader.getReadableMediaTypes().stream())
.flatMap(reader -> reader.getReadableMediaTypes(elementType).stream())
.collect(Collectors.toList());
return errorFunction.apply(
new UnsupportedMediaTypeException(contentType, mediaTypes, elementType));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -385,7 +385,7 @@ public abstract class BodyInserters {
BodyInserter.Context context, @Nullable MediaType mediaType) {
List<MediaType> supportedMediaTypes = context.messageWriters().stream()
.flatMap(reader -> reader.getWritableMediaTypes().stream())
.flatMap(reader -> reader.getWritableMediaTypes(bodyType).stream())
.collect(Collectors.toList());
return new UnsupportedMediaTypeException(mediaType, supportedMediaTypes, bodyType);

View File

@@ -17,11 +17,11 @@
package org.springframework.web.reactive.result.method.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -76,8 +76,6 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
private final List<HttpMessageReader<?>> messageReaders;
private final List<MediaType> supportedMediaTypes;
/**
* Constructor with {@link HttpMessageReader}'s and a {@link Validator}.
@@ -99,9 +97,6 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
Assert.notEmpty(messageReaders, "At least one HttpMessageReader is required");
Assert.notNull(adapterRegistry, "ReactiveAdapterRegistry is required");
this.messageReaders = messageReaders;
this.supportedMediaTypes = messageReaders.stream()
.flatMap(converter -> converter.getReadableMediaTypes().stream())
.collect(Collectors.toList());
}
@@ -212,8 +207,9 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
if (contentType == null && method != null && SUPPORTED_METHODS.contains(method)) {
Flux<DataBuffer> body = request.getBody().doOnNext(buffer -> {
DataBufferUtils.release(buffer);
// Body not empty, back to 415..
throw new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes, elementType);
// Body not empty, back toy 415..
throw new UnsupportedMediaTypeStatusException(
mediaType, getSupportedMediaTypes(elementType), elementType);
});
if (isBodyRequired) {
body = body.switchIfEmpty(Mono.error(() -> handleMissingBody(bodyParam)));
@@ -221,7 +217,8 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
return (adapter != null ? Mono.just(adapter.fromPublisher(body)) : Mono.from(body));
}
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes, elementType));
return Mono.error(new UnsupportedMediaTypeStatusException(
mediaType, getSupportedMediaTypes(elementType), elementType));
}
private Throwable handleReadError(MethodParameter parameter, Throwable ex) {
@@ -263,4 +260,12 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
}
}
private List<MediaType> getSupportedMediaTypes(ResolvableType elementType) {
List<MediaType> mediaTypes = new ArrayList<>();
for (HttpMessageReader<?> reader : this.messageReaders) {
mediaTypes.addAll(reader.getReadableMediaTypes(elementType));
}
return mediaTypes;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -192,7 +192,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa
List<MediaType> writableMediaTypes = new ArrayList<>();
for (HttpMessageWriter<?> converter : getMessageWriters()) {
if (converter.canWrite(elementType, null)) {
writableMediaTypes.addAll(converter.getWritableMediaTypes());
writableMediaTypes.addAll(converter.getWritableMediaTypes(elementType));
}
}
return writableMediaTypes;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -28,6 +28,8 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import org.junit.jupiter.api.BeforeEach;
@@ -78,6 +80,9 @@ import static org.springframework.web.testfixture.method.ResolvableMethod.on;
*/
public class ResponseEntityResultHandlerTests {
private static final String NEWLINE_SYSTEM_PROPERTY = System.getProperty("line.separator");
private ResponseEntityResultHandler resultHandler;
@@ -393,6 +398,37 @@ public class ResponseEntityResultHandlerTests {
.verify();
}
@Test // gh-26212
public void handleWithObjectMapperByTypeRegistration() throws Exception {
MediaType halFormsMediaType = MediaType.parseMediaType("application/prs.hal-forms+json");
MediaType halMediaType = MediaType.parseMediaType("application/hal+json");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
encoder.registerObjectMappersForType(Person.class, map -> map.put(halMediaType, objectMapper));
EncoderHttpMessageWriter<?> writer = new EncoderHttpMessageWriter<>(encoder);
ResponseEntityResultHandler handler = new ResponseEntityResultHandler(
Collections.singletonList(writer), new RequestedContentTypeResolverBuilder().build());
MockServerWebExchange exchange = MockServerWebExchange.from(
get("/path").header("Accept", halFormsMediaType + "," + halMediaType));
ResponseEntity<Person> value = ResponseEntity.ok().body(new Person("Jason"));
MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Person.class));
HandlerResult result = handlerResult(value, returnType);
handler.handleResult(exchange, result).block();
assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(halMediaType);
assertThat(exchange.getResponse().getBodyAsString().block()).isEqualTo(
"{" + NEWLINE_SYSTEM_PROPERTY +
" \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY +
"}");
}
private void testHandle(Object returnValue, MethodParameter returnType) {
MockServerWebExchange exchange = MockServerWebExchange.from(get("/path"));
@@ -451,6 +487,8 @@ public class ResponseEntityResultHandlerTests {
ResponseEntity<Void> responseEntityVoid() { return null; }
ResponseEntity<Person> responseEntityPerson() { return null; }
HttpHeaders httpHeaders() { return null; }
Mono<ResponseEntity<String>> mono() { return null; }
@@ -470,4 +508,26 @@ public class ResponseEntityResultHandlerTests {
Object object() { return null; }
}
@SuppressWarnings("unused")
private static class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}