Вам нужен этот класс как конфигурация
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()
}
}