首页 > 解决方案 > 文件上传导致 HTTP 415 Unsupported Media Type with RouterFunction 和 HandlerFunction

问题描述

尽管将内容类型设置为 MULTIPART_FORM_DATA,但测试请求将被 HTTP 415 拒绝:

路由器功能:

@Configuration
class Router(
    private val uploadHandler: UploadHandler
) {
    @ExperimentalPathApi
    @Bean
    fun route() = coRouter {
        POST("/upload", accept(MediaType.MULTIPART_FORM_DATA), uploadHandler::upload)
    }
}

处理函数:

@Component
class UploadHandler(
    private val uploadVerifier: UploadVerifier,
    private val excelTypeResolver: ExcelTypeResolver
) {
    @ExperimentalPathApi
    suspend fun upload(serverRequest: ServerRequest): ServerResponse {
        val now = LocalDateTime.now()
        val tmpDir = createTempDirectory("smp-excel-import-$now")
        val result = serverRequest.bodyToFlux(FilePart::class.java)
            .flatMap { filePart ->
                val path = "${tmpDir.name}/${filePart.name()}"
                filePart.transferTo(Path.of(path))
                    .map { excelTypeResolver.resolve(path) }
            }
            .collect(Collectors.toUnmodifiableSet())
            .flatMap { Mono.just(uploadVerifier.verify(it)) }

        return if (result.awaitSingle()) {
            ServerResponse.ok().buildAndAwait()
        } else {
            ServerResponse.badRequest().buildAndAwait()
        }

    }
}

测试:

@IntegrationTest
class UploadHandlerTest {
    @Inject
    private lateinit var webTestClient: WebTestClient

    @Test
    fun upload() {
        val builder = MultipartBodyBuilder()
        builder.part("files", ClassPathResource("CN42N.xlsx")).contentType(MULTIPART_FORM_DATA)
        webTestClient.post()
            .uri("/upload")
            .accept(ALL)
            .contentType(MULTIPART_FORM_DATA)
            .body(BodyInserters.fromMultipartData(builder.build()))
            .exchange()
            .expectStatus()
            .is2xxSuccessful()
    }
}

输出:

2021-02-10 12:07:05.729 [main] ERROR o.s.t.w.r.server.ExchangeResult - Request details for assertion failure:

> POST http://localhost:51485/upload
> WebTestClient-Request-Id: [1]
> Accept: [*/*]
> Content-Type: [multipart/form-data;boundary=Q1BTQgiOIvoHF6eTGJMNuKrS7oOxo1nL8M1X]

1022477 bytes of content.

< 415 UNSUPPORTED_MEDIA_TYPE Unsupported Media Type
< Vary: [Origin, Access-Control-Request-Method, Access-Control-Request-Headers]
< Content-Type: [application/json]
< Content-Length: [146]
< Cache-Control: [no-cache, no-store, max-age=0, must-revalidate]
< Pragma: [no-cache]
< Expires: [0]
< X-Content-Type-Options: [nosniff]
< X-Frame-Options: [DENY]
< X-XSS-Protection: [1 ; mode=block]
< Referrer-Policy: [no-referrer]

{"timestamp":"2021-02-10T11:07:05.650+00:00","path":"/upload","status":415,"error":"Unsupported Media Type","message":"","requestId":"6d0c12f4-1"}

所以客户端请求是正确的,但 Content-Type 配置似乎缺少某处。如果我手动向正在运行的服务器发布请求,行为是相同的。此外,将媒体类型更改为 ALL 没有效果。

标签: kotlinfile-uploadspring-webflux

解决方案


原因是:

val result = serverRequest.bodyToFlux(FilePart::class.java)

它适用于:

val result = serverRequest.bodyToFlux(Part::class.java)

推荐阅读