InvalidMediaTypeException: Invalid mime type \"text/plain; charset=x-user-defined\": unsupported charset 'x-user-defined'
Describe the bug
Upgrade spring-cloud-starter-gateway-mvc from 4.1.2 to 4.2.0, an InvalidMediaTypeException occurred in the gateway application.
For details, we have a Web UI which sends Ajax request with mimeType = 'text/plain; charset=x-user-defined' to the gateway application, the request would be routed to another service behind and response with file content that being encrypted(this is why we use the non standard charset x-user-defined). The error logs are:
org.springframework.http.InvalidMediaTypeException: Invalid mime type "text/plain; charset=x-user-defined": unsupported charset 'x-user-defined' at org.springframework.http.MediaType.parseMediaType(MediaType.java:760) ~[spring-web-6.2.2.jar:6.2.2] at org.springframework.http.HttpHeaders.getContentType(HttpHeaders.java:1048) ~[spring-web-6.2.2.jar:6.2.2] at org.springframework.http.ReadOnlyHttpHeaders.getContentType(ReadOnlyHttpHeaders.java:65) ~[spring-web-6.2.2.jar:6.2.2] at org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange.copyResponseBody(AbstractProxyExchange.java:45) ~[spring-cloud-gateway-server-mvc-4.2.0.jar:4.2.0] at org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange.lambda$doExchange$3(RestClientProxyExchange.java:83) ~[spring-cloud-gateway-server-mvc-4.2.0.jar:4.2.0] at org.springframework.cloud.gateway.server.mvc.handler.GatewayServerResponseBuilder$WriteFunctionResponse.writeToInternal(GatewayServerResponseBuilder.java:231) ~[spring-cloud-gateway-server-mvc-4.2.0.jar:4.2.0] at org.springframework.cloud.gateway.server.mvc.handler.AbstractGatewayServerResponse.writeTo(AbstractGatewayServerResponse.java:103) ~[spring-cloud-gateway-server-mvc-4.2.0.jar:4.2.0] at org.springframework.web.servlet.function.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:112) ~[spring-webmvc-6.2.2.jar:6.2.2] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088) ~[spring-webmvc-6.2.2.jar:6.2.2] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978) ~[spring-webmvc-6.2.2.jar:6.2.2] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.2.jar:6.2.2] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.2.2.jar:6.2.2] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[servlet-api.jar:6.0] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.2.2.jar:6.2.2] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[servlet-api.jar:6.0] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[catalina.jar:10.1.31] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[catalina.jar:10.1.31] ...
It looks like the new version of spring-cloud-starter-gateway-mvc has added something kind of validation to check if the Http response contains non-standard mime type or charset. In our case, the service behind gateway would return an Http header Content-Type: 'text/plain; charset=x-user-defined'.
I've looked into the AbstractProxyExchange#copyResponseBody method, this was introduced in 4.1.6 to support some streaming media types -
AbstractProxyExchange.java
protected int copyResponseBody(ClientHttpResponse clientResponse, InputStream inputStream,
OutputStream outputStream) throws IOException {
...
int transferredBytes;
if (properties.getStreamingMediaTypes().contains(clientResponse.getHeaders().getContentType())) {
transferredBytes = copyResponseBodyWithFlushing(inputStream, outputStream);
}
else {
transferredBytes = StreamUtils.copy(inputStream, outputStream);
}
return transferredBytes;
}
The error was caused by clientResponse.getHeaders().getContentType(), this de facto checks that all Http responses do not contain a charset that is not part of the JRE standard, which is a breaking change to some legacy or specialized services behind Spring Cloud Gateway.
In this case, I expect it goes with the else block for just doing IO without validation. I can override this with a customized RestClientProxyExchange bean as a temp workaround though, would you consider to support non-standard charsets or media types in the Content-Type header in a future release? Thanks!
Sample N/A