UnsupportedMediaTypeException с Spring и Reactor - PullRequest
0 голосов
/ 06 мая 2020

Я попытался передать файл с помощью Spring и реактора, используя класс ResourceRegion.

У меня есть этот метод:

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

и все запускается под встроенной Netty SpringBoot, но когда я пытаюсь вызвать 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 ....), но проблема с типом содержимого все еще не устранена.

Есть идеи?

1 Ответ

0 голосов
/ 28 августа 2020

Вам нужен этот класс как конфигурация

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()
    }
}
...