【问题标题】:UnsupportedMediaTypeException with Spring and Reactor带有 Spring 和 Reactor 的 UnsupportedMediaTypeException
【发布时间】:2020-08-21 03:16:09
【问题描述】:

我尝试使用类 ResourceRegion 使用 Spring 和反应器流式传输文件。

我有这个方法:

public Mono<ServerResponse> getPartialVideoById(ServerRequest request) {

        Long idVideo = Long.valueOf(request.pathVariable("idVideo"));

        HttpHeaders requestHeaders = request.headers().asHttpHeaders();

        Optional<UrlResource> video = this.videoService.getUrlResourceById(idVideo);

        Optional<ResourceRegion> resourceRegion = video.map(v -> {
            try {
                return this.videoService.getRegion(v, requestHeaders);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        });
        return ServerResponse.status(HttpStatus.PARTIAL_CONTENT)
                .contentType(MediaTypeFactory.getMediaType(video.get()).orElse(MediaType.APPLICATION_OCTET_STREAM))
                .contentLength(resourceRegion.get().getCount())
                .headers(headers -> headers.setCacheControl(CacheControl.noCache()))
                .body(Mono.just(resourceRegion.get()), ResourceRegion.class).flatMap(response -> {
                    if (response.headers().getContentLength() == 0) {
                        return Mono.error(new ResourceNotFound());
                    }
                    return Mono.just(response);
                });
    }

我的终点是这样的:

@Bean
        RouterFunction<ServerResponse> videoEndPoint(VideoRouteHandler videoRouteHandler) {

            return route(GET("/video/{idVideo}"), videoRouteHandler::getPartialVideoById)
                    .filter((request, next) -> next.handle(request)
                            .onErrorResume(ErrorHandler::handleError));    
       }

所有都是在SpringBoot的嵌入式Netty下启动的,但是当我尝试调用该api时,我不得不出现以下错误:

org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'video/x-msvideo' not supported for bodyType=org.springframework.core.io.support.ResourceRegion

我尝试了不同类型的文件(avi、mkv、mp4、....还有 pdf....),但内容类型仍然存在问题。

有什么想法吗?

【问题讨论】:

    标签: java spring spring-boot reactor


    【解决方案1】:

    你需要这个类作为配置

    import org.springframework.context.annotation.Configuration
    import org.springframework.http.codec.ServerCodecConfigurer
    import org.springframework.web.reactive.config.WebFluxConfigurer
    
    
    @Configuration
    class WebFluxConfig: WebFluxConfigurer {
    
        override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
            configurer.customCodecs().writer(ResourceRegionMessageWriter())
        }
    }
    

    还有作者……

    import org.reactivestreams.Publisher
    import org.springframework.core.ResolvableType
    import org.springframework.core.codec.ResourceRegionEncoder
    import org.springframework.core.io.Resource
    import org.springframework.core.io.support.ResourceRegion
    import org.springframework.http.HttpHeaders
    import org.springframework.http.MediaType
    import org.springframework.http.MediaTypeFactory
    import org.springframework.http.ReactiveHttpOutputMessage
    import org.springframework.http.ZeroCopyHttpOutputMessage
    import org.springframework.http.codec.HttpMessageWriter
    import org.springframework.http.server.reactive.ServerHttpRequest
    import org.springframework.http.server.reactive.ServerHttpResponse
    import reactor.core.publisher.Mono
    import java.io.File
    import java.lang.Long.min
    import java.util.*
    
    
    class ResourceRegionMessageWriter : HttpMessageWriter<ResourceRegion> {
    
    
        private val log = logger()
    
    
        private val resourceRegionEncoder = ResourceRegionEncoder()
    
    
        override fun getWritableMediaTypes(): MutableList<MediaType> {
    
            return MediaType.asMediaTypes(resourceRegionEncoder.encodableMimeTypes)
        }
    
        override fun canWrite(elementType: ResolvableType, mediaType: MediaType?): Boolean {
    
            return resourceRegionEncoder.canEncode(elementType, mediaType)
        }
    
        override fun write(inputStream: Publisher<out ResourceRegion>, elementType: ResolvableType, mediaType: MediaType?, message:     ReactiveHttpOutputMessage, hints: MutableMap<String, Any>): Mono<Void> {
            TODO("Not yet implemented")
        }
    
        override fun write(inputStream: Publisher<out ResourceRegion>,
                                   actualType: ResolvableType,
                                   elementType: ResolvableType,
                                   mediaType: MediaType?,
                                   request: ServerHttpRequest,
                                   response: ServerHttpResponse,
                                   hints: Map<String, Any>): Mono<Void> {
    
    
            val headers: HttpHeaders = response.headers
            headers[HttpHeaders.ACCEPT_RANGES] = "bytes"
    
            return Mono.from(inputStream).flatMap { resourceRegion: ResourceRegion ->
    
                val contentLength = headers.contentLength
                val requestHeaders: HttpHeaders = request.headers
                val range = if (requestHeaders.range.isNotEmpty()) requestHeaders.range[0] else null
    
                if (range != null) {
    
                    val start = range.getRangeStart(contentLength)
                    val end: Long = min(start + resourceRegion.count - 1, contentLength - 1)
    
                    val contentRange = "bytes $start-$end/$contentLength"
                    log.debug("contentRange: $contentRange")
    
                    headers.add(HttpHeaders.CONTENT_RANGE, contentRange)
                    headers.contentLength = end - start + 1
                }
    
                val resourceMediaType = getResourceMediaType(mediaType, resourceRegion.resource)
    
                zeroCopy(resourceRegion.resource, resourceRegion, response).orElseGet {
    
                    val input = Mono.just(resourceRegion)
                    val body = resourceRegionEncoder.encode(input, response.bufferFactory(),
                            ResolvableType.forClass(ResourceRegion::class.java), resourceMediaType, Collections.emptyMap())
    
                    response.writeWith(body)
                }
    
            }.doOnError { obj: Throwable -> obj.printStackTrace() }
    
        }
    
        private fun getResourceMediaType(mediaType: MediaType?, resource: Resource): MediaType? {
    
            return if (Objects.nonNull(mediaType) && mediaType!!.isConcrete && mediaType !== MediaType.APPLICATION_OCTET_STREAM) {
                mediaType
            } else {
                MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)
            }
        }
    
        private fun zeroCopy(resource: Resource, resourceRegion: ResourceRegion,
                             message: ReactiveHttpOutputMessage): Optional<Mono<Void>> {
    
            if (message is ZeroCopyHttpOutputMessage && resource.isFile) {
    
                try {
    
                    val file: File = resource.file
                    val position = resourceRegion.position
                    val count = resourceRegion.count
    
                    return Optional.of(message.writeWith(file, position, count))
    
                } catch (ignored: Exception) {}
            }
    
            return Optional.empty()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-10
      • 1970-01-01
      • 1970-01-01
      • 2013-11-13
      • 2020-12-06
      • 2021-07-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多