文件上传结果为 HTTP 415 Unsupported Media Type with RouterFunction and HandlerFunction
File upload results in HTTP 415 Unsupported Media Type with RouterFunction and 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的配置好像哪里不对。
如果我 post 对 运行 服务器手动请求,行为是相同的。
此外,将媒体类型更改为 ALL 也没有效果。
原因是:
val result = serverRequest.bodyToFlux(FilePart::class.java)
适用于:
val result = serverRequest.bodyToFlux(Part::class.java)
尽管内容类型设置为 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的配置好像哪里不对。 如果我 post 对 运行 服务器手动请求,行为是相同的。 此外,将媒体类型更改为 ALL 也没有效果。
原因是:
val result = serverRequest.bodyToFlux(FilePart::class.java)
适用于:
val result = serverRequest.bodyToFlux(Part::class.java)