Issue
I'm trying to build a spring controller which essentially acts as a reverse-proxy for a geoserver instance.
For example if the client wants to access geoserver/wms?PARAM1=foo&PARAM2=bar
, the controller will simply forward the request to the actual geoserver instance and serve back the response. In my case, geoserver either returns an XML payload or an image.
When testing this controller with an URL which returns an image, I am able to process the initial client request, forward it to geoserver and then process it but I'm getting the following error when serving the response to the client :
There was an unexpected error (type=Internal Server Error, status=500).
No converter for [class [B] with preset Content-Type 'image/png'
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class [B] with preset Content-Type 'image/png'
Here is the controller class:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@BasePathAwareController
public class GeoServerProxyController {
//only used to check which converters are present
@Autowired
List<HttpMessageConverter<?>> converters;
@RequestMapping(value = "/geoserver/**")
@ResponseBody
public ResponseEntity<byte[]> forwardRequest(@RequestParam MultiValueMap<String, String> requestParams, @RequestHeader Map<String, String> headers, HttpServletRequest request) {
WebClient client = WebClient.builder()
.baseUrl("http://127.0.0.1:8090/geoserver/")
.build();
String url = new AntPathMatcher().extractPathWithinPattern("/geoserver/**", request.getRequestURI());
WebClient.ResponseSpec response = client.get()
.uri(uriBuilder -> uriBuilder
.path(url)
.queryParams(requestParams)
.build())
.headers(httpHeaders -> {
headers.forEach(httpHeaders::set);
})
.retrieve();
HttpHeaders responseHeaders = response.toBodilessEntity().map(HttpEntity::getHeaders).block();
byte[] responseBody = response.bodyToMono(byte[].class).block();
return ResponseEntity.ok().headers(responseHeaders).body(responseBody);
}
As advised in another thread, I have tried registering a byte array http message converter, which I was able to confirm is added to the list of http message converters.
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
final List<MediaType> list = new ArrayList<>();
list.add(MediaType.IMAGE_JPEG);
list.add(MediaType.IMAGE_PNG);
list.add(MediaType.APPLICATION_OCTET_STREAM);
arrayHttpMessageConverter.setSupportedMediaTypes(list);
converters.add(arrayHttpMessageConverter);
}
}
This resulted in the same error.
I have also tried using InputStreamResource
as a return type, as recommended by this article. It resulted in the same kind of error except with InputStreamResource
instead of class [B]
.
I have also tried adding the following annotation to my controller method (which wasn't optimal as I would prefer not specifying a constant content type) :
@RequestMapping(value = "/geoserver/**", produces=MediaType.IMAGE_PNG_VALUE)
This also results in the exact same error.
I was not able to find a solution to this problem in other threads or in spring web documentation. The most common problem that somewhat resembles this deals with a "Content-Type=null" header, which is not my case.
Does anyone know how to solve this error ? Alternatively, is there a better way to serve distant image files through a Spring controller ?
Solution
The @ResponseBody
annotation is used to serialize a Java object to the response (typically as JSON or XML).
Here you don't want to convert anything, just send raw binary content. Remove the annotation.
You should also change @RestController
to @Controller
, because @RestController
automatically adds @ResponseBody
.
Answered By - Olivier
Answer Checked By - David Goodson (JavaFixing Volunteer)