如何解决Spring在尝试服务镜像时出现的[CLASS[B]WITH PRESET CONTENT-Type‘IMAGE/PNG’“错误?

ctrmrzij  于 2022-10-04  发布在  Spring
关注(0)|答案(3)|浏览(569)

我正在尝试构建一个Spring控制器,它实际上充当Geoserver示例的反向代理。例如,如果客户端想要访问geoserver/wms?PARAM1=foo&PARAM2=bar,控制器将简单地将请求转发到实际的Geoserver示例并返回响应。在我的例子中,Geoserver要么返回一个XML有效负载,要么返回一个图像。

在使用返回图像的URL测试此控制器时,我能够处理初始客户端请求,将其转发到Geoserver,然后进行处理,但在向客户端提供响应时出现以下错误:

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'

Full stack trace

下面是控制器类:

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);
    }

正如在另一个线程中所建议的,我已经尝试注册一个字节数组http消息转换器,我能够确认它已添加到http消息转换器列表中。

@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 article的建议,使用dInputStreamResource作为返回类型。除了使用InputStreamResource而不是class [B]之外,它会导致相同类型的错误。

我还尝试将以下注解添加到我的控制器方法中(这并不是最优的,因为我不希望指定常量内容类型):

@RequestMapping(value = "/geoserver/**", produces=MediaType.IMAGE_PNG_VALUE)

这也会导致完全相同的错误。
在其他帖子或Spring Web文档中,我找不到这个问题的解决方案。与此有些类似的最常见问题是处理“Content-Type=NULL”标头,这不是我的情况。

有人知道如何解决这个错误吗?或者,有没有更好的方式通过Spring控制器提供远程图像文件?

ars1skjm

ars1skjm1#

@ResponseBody注解用于将Java对象序列化为响应(通常为JSON或XML)。在这里,您不想转换任何内容,只需要发送原始的二进制内容。删除注解。

您还应该将@RestController更改为@Controller,因为@RestController会自动添加@ResponseBody

2exbekwf

2exbekwf2#

看起来您面临的问题是由于@BasePathAwareController造成的,移除它,您就可以开始工作了。

描述:

基于共享的堆栈跟踪:

发生这种情况时,SpringDataRest正在尝试将适当的对象Map到它找不到的content-type。因此抛出一个通用的500异常。

b4wnujal

b4wnujal3#

对于那些感兴趣的人:我有过同样的问题,就像这样解决它。此代码不是最终代码,仅用于测试我的情况。

棱角前端开口层

瓷砖层:我定义了定制的tileLoadFunction(示例可以在Open Layers文档中找到),url调用我的SprringBoot后端API,其中代理Geoserver后端,在FETCH报头中发送JWT令牌(出于安全目的,非强制)

new TileLayer({
      source: new TileWMS({
        url: 'http://localhost:8080/api/v1/map/wms',
        tileLoadFunction: (tile, src) => {
          const retryCodes = [408, 429, 500, 502, 503, 504];
          const retries: any = {};
          const image: any = (tile as ImageTile).getImage();
          fetch(src, {
            headers: {
              Authorization: 'Bearer ' + this.oauthService.getAccessToken(),
            },
          })
            .then((response) => {
              if (retryCodes.includes(response.status)) {
                retries[src] = (retries[src] || 0) + 1;
                if (retries[src] <= 3) {
                  setTimeout(() => tile.load(), retries[src] * 1000);
                }
                return Promise.reject();
              }
              return response.blob();
            })
            .then((blob) => {
              const imageUrl = URL.createObjectURL(blob);
              image.src = imageUrl;
              setTimeout(() => URL.revokeObjectURL(imageUrl), 5000);
            })
            .catch(() => tile.setState(3)); // error
        },
        params: {
          LAYERS: 'TEST:section_33',
          TILED: true,
        },
        serverType: 'geoserver',
        transition: 0,
      }),

SpringBoot后端

我有一个RestController端点,它离第一个帖子很近,对我来说一切都很好:WMS Geoserver切片由我的前端调用,bakend拦截这些调用并发出Geoserver请求,最后将切片发送回前端,WMS图层显示在Map上

@RestController
@RequestMapping("/api/v1/map")
@Slf4j
public class MapController {

    @GetMapping(value = "/wms")
    public ResponseEntity<byte[]> getWmsTile(@RequestParam MultiValueMap<String, String> requestParams, @RequestHeader Map<String, String> headers, HttpServletRequest request) throws IOException {

        WebClient client = WebClient.builder()
                .baseUrl("http://localhost:7070/geoserver/TEST/wms")
                .build();

        WebClient.ResponseSpec response = client.get()
                .uri(uriBuilder -> uriBuilder
                        .path("")
                        .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().contentType(MediaType.IMAGE_PNG).body(responseBody);
    }

}

相关问题