You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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注解

核心问题分析

  1. WebClient响应流重复消费:原代码中先后调用response.toBodilessEntity().block()和response.bodyToMono(byte[].class).block(),ResponseSpec的响应流是一次性的,第一次调用已消费并关闭流,导致第二次读取失败,同时响应头获取不完整。
  2. 消息转换器注册方式错误:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 21:45:25