Spring代理GeoServer返回图片时遇No converter错误的解决方法
Spring反向代理GeoServer返回图片时出现500错误的解决方法
问题背景
构建Spring控制器作为GeoServer的反向代理,客户端请求geoserver/wms?PARAM1=foo&PARAM2=bar时,控制器转发请求到实际GeoServer实例并返回响应。GeoServer会返回XML负载或图片,但测试图片返回URL时,出现以下500错误:
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'
已尝试的方案均未解决问题:
- 注册
ByteArrayHttpMessageConverter并确认已添加到转换器列表 - 使用
InputStreamResource作为返回类型 - 为控制器方法添加
produces=MediaType.IMAGE_PNG_VALUE注解
核心问题分析
- WebClient响应流重复消费:原代码中先后调用
response.toBodilessEntity().block()和response.bodyToMono(byte[].class).block(),ResponseSpec的响应流是一次性的,第一次调用已消费并关闭流,导致第二次读取失败,同时响应头获取不完整。 - 消息转换器注册方式错误:使用
configureMessageConverters会替换Spring默认转换器列表,可能丢失其他必要转换器,导致无法匹配byte[]与image/png类型。
解决方案
1. 修复WebClient响应读取逻辑
将响应头和响应体的读取合并为一次操作,避免重复消费响应流,同时复用GeoServer返回的完整响应头和状态码:
@Slf4j @RestController @BasePathAwareController public class GeoServerProxyController { // 全局初始化WebClient,避免每次请求重复创建 private final WebClient webClient; public GeoServerProxyController() { this.webClient = WebClient.builder() .baseUrl("http://127.0.0.1:8090/geoserver/") .build(); } @RequestMapping(value = "/geoserver/**") public ResponseEntity<byte[]> forwardRequest(@RequestParam MultiValueMap<String, String> requestParams, @RequestHeader Map<String, String> headers, HttpServletRequest request) { String url = new AntPathMatcher().extractPathWithinPattern("/geoserver/**", request.getRequestURI()); // 一次性获取完整响应(头+体) ResponseEntity<byte[]> geoServerResponse = webClient.get() .uri(uriBuilder -> uriBuilder .path(url) .queryParams(requestParams) .build()) .headers(httpHeaders -> headers.forEach(httpHeaders::set)) .retrieve() .toEntity(byte[].class) .block(); if (geoServerResponse == null) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } // 复用GeoServer的响应状态、头和体返回给客户端 return ResponseEntity.status(geoServerResponse.getStatusCode()) .headers(geoServerResponse.getHeaders()) .body(geoServerResponse.getBody()); } }
2. 调整消息转换器注册方式
改用extendMessageConverters替代configureMessageConverters,在默认转换器列表基础上添加自定义转换器,避免丢失默认配置:
@Configuration public class WebMVCConfig implements WebMvcConfigurer { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { ByteArrayHttpMessageConverter byteArrayConverter = new ByteArrayHttpMessageConverter(); List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.IMAGE_JPEG); supportedMediaTypes.add(MediaType.IMAGE_PNG); supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); supportedMediaTypes.add(MediaType.APPLICATION_XML); // 兼容GeoServer的XML返回 byteArrayConverter.setSupportedMediaTypes(supportedMediaTypes); converters.add(byteArrayConverter); } }
3. 可选:使用InputStreamResource优化内存占用
如果返回的图片较大,可改用InputStreamResource,无需将整个文件加载到内存,提升性能:
@RequestMapping(value = "/geoserver/**") public ResponseEntity<InputStreamResource> forwardRequest(@RequestParam MultiValueMap<String, String> requestParams, @RequestHeader Map<String, String> headers, HttpServletRequest request) { String url = new AntPathMatcher().extractPathWithinPattern("/geoserver/**", request.getRequestURI()); ResponseEntity<InputStreamResource> geoServerResponse = webClient.get() .uri(uriBuilder -> uriBuilder .path(url) .queryParams(requestParams) .build()) .headers(httpHeaders -> headers.forEach(httpHeaders::set)) .retrieve() .toEntity(InputStreamResource.class) .block(); if (geoServerResponse == null) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } return ResponseEntity.status(geoServerResponse.getStatusCode()) .headers(geoServerResponse.getHeaders()) .body(geoServerResponse.getBody()); }
说明
- 原错误的核心原因是WebClient响应流被重复消费,导致后续无法正确读取响应体,进而引发转换器匹配异常。
- 使用
extendMessageConverters能保证Spring默认的转换器(如处理XML的转换器)不丢失,同时添加自定义的字节数组转换器。
内容的提问来源于stack exchange,提问作者Angela Lopez
相关产品推荐
相关产品推荐

