Quarkus JAX-RS 服务多部分表单上传 - "Cannot consume content type"
Quarkus JAX-RS Service Multipart Form Upload - "Cannot consume content type"
我正在使用 Quarkus 编写 JAX-RS 服务。我的服务应该创建一个简单的用户配置文件,其中包含姓名、出生日期等,以及一个上传图像文件。我正在尝试将其作为多部分表单上传来执行此操作,但 运行 会出错。
我的服务定义看起来像这样...
@Path("/profile")
@POST
@Consumes({MediaType.MULTIPART_FORM_DATA})
@Produces({ MediaType.APPLICATION_JSON })
public Response createStudentProfile(@MultipartForm ProfileFormBean profile);
我的ProfileFormBean
class看起来像这样...
public class ProfileFormBean {
@FormParam("firstName")
@PartType(MediaType.TEXT_PLAIN)
private String firstName = null;
@FormParam("lastName")
@PartType(MediaType.TEXT_PLAIN)
private String lastName = null;
@FormParam("profileImage")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
private byte[] profileImage = null;
...
}
据我所知,这应该足以让我的服务接受文件上传。我正在尝试使用 curl 对此进行测试,但是当我这样做时出现异常。我的 curl 命令只是尝试上传文件本身,而不是设置 first/last 名称属性:
curl -vki -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H 'Accepts: application/json' \
-F 'profileImage=@profile_image.jpg' \
http://localhost:8081/profile
此命令的输出如下所示:
curl -vki -H "Authorization: Bearer ${ACCESS_TOKEN}" \
> -H 'Accepts: application/json' \
> -F 'profileImage=@profile_image.jpg' \
> http://localhost:8081/profile
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> POST /profile HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1N...
> Accepts: application/json
> Content-Length: 11701
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------49161ebc82f19511
>
< HTTP/1.1 415 Unsupported Media Type
HTTP/1.1 415 Unsupported Media Type
< Content-Length: 0
Content-Length: 0
* HTTP error before end of send, stop sending
<
* Closing connection 0
当我 运行 在服务器上看到以下异常时:
15:26:35 DEBUG [org.jboss.resteasy.resteasy_jaxrs.i18n] RESTEASY002305: Failed executing POST /profile: javax.ws.rs.NotSupportedException: RESTEASY003065: Cannot consume content type
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:453)
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:155)
at org.jboss.resteasy.core.registry.RootNode.match(RootNode.java:58)
at org.jboss.resteasy.core.registry.RootClassNode.match(RootClassNode.java:47)
at org.jboss.resteasy.core.ResourceMethodRegistry.getResourceInvoker(ResourceMethodRegistry.java:482)
at org.jboss.resteasy.core.SynchronousDispatcher.getInvoker(SynchronousDispatcher.java:323)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke(SynchronousDispatcher.java:244)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess[=15=](SynchronousDispatcher.java:153)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:30)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:63)
at io.undertow.servlet.handlers.ServletChain.handleRequest(ServletChain.java:68)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:133)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:65)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:270)
at io.undertow.servlet.handlers.ServletInitialHandler.access0(ServletInitialHandler.java:59)
at io.undertow.servlet.handlers.ServletInitialHandler.call(ServletInitialHandler.java:116)
at io.undertow.servlet.handlers.ServletInitialHandler.call(ServletInitialHandler.java:113)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction.call(ContextClassLoaderSetupAction.java:43)
at io.quarkus.undertow.runtime.UndertowDeploymentRecorder.call(UndertowDeploymentRecorder.java:476)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:250)
at io.undertow.servlet.handlers.ServletInitialHandler.access[=15=]0(ServletInitialHandler.java:59)
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:82)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:290)
at io.undertow.server.HttpServerExchange.run(HttpServerExchange.java:669)
at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:224)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at java.base/java.lang.Thread.run(Thread.java:830)
at org.jboss.threads.JBossThread.run(JBossThread.java:479)
我错过了什么吗?我在 Quarkus 站点上找到了 creating REST clients with multipart 的文档,但没有找到服务器端资源的文档。我还需要做些什么才能让它正常工作吗?
问题在于
@FormParam("profileImage")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
private byte[] profileImage = null;
您在宣传此部分允许的唯一内容类型是 application/octet-stream
。但是您正在发送图像,而 cURL 并未将图像的内容类型设置为该图像。我可以想象它在幕后将其设置为 image/jpg
。您可以手动设置类型(这将修复 this 请求)
profileImage=@profile_image.jpg;type=application/octet-stream
但就个人而言,我会删除 @PartType
。由于字段的类型已经是byte[]
,所以转换不会有问题。此外,许多客户无法设置各个部分的内容类型。就像 browser/JS 一样,客户端将默认设置文件的内容类型,就像 cURL 所做的那样。
我正在使用 Quarkus 编写 JAX-RS 服务。我的服务应该创建一个简单的用户配置文件,其中包含姓名、出生日期等,以及一个上传图像文件。我正在尝试将其作为多部分表单上传来执行此操作,但 运行 会出错。
我的服务定义看起来像这样...
@Path("/profile")
@POST
@Consumes({MediaType.MULTIPART_FORM_DATA})
@Produces({ MediaType.APPLICATION_JSON })
public Response createStudentProfile(@MultipartForm ProfileFormBean profile);
我的ProfileFormBean
class看起来像这样...
public class ProfileFormBean {
@FormParam("firstName")
@PartType(MediaType.TEXT_PLAIN)
private String firstName = null;
@FormParam("lastName")
@PartType(MediaType.TEXT_PLAIN)
private String lastName = null;
@FormParam("profileImage")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
private byte[] profileImage = null;
...
}
据我所知,这应该足以让我的服务接受文件上传。我正在尝试使用 curl 对此进行测试,但是当我这样做时出现异常。我的 curl 命令只是尝试上传文件本身,而不是设置 first/last 名称属性:
curl -vki -H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H 'Accepts: application/json' \
-F 'profileImage=@profile_image.jpg' \
http://localhost:8081/profile
此命令的输出如下所示:
curl -vki -H "Authorization: Bearer ${ACCESS_TOKEN}" \
> -H 'Accepts: application/json' \
> -F 'profileImage=@profile_image.jpg' \
> http://localhost:8081/profile
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> POST /profile HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1N...
> Accepts: application/json
> Content-Length: 11701
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------49161ebc82f19511
>
< HTTP/1.1 415 Unsupported Media Type
HTTP/1.1 415 Unsupported Media Type
< Content-Length: 0
Content-Length: 0
* HTTP error before end of send, stop sending
<
* Closing connection 0
当我 运行 在服务器上看到以下异常时:
15:26:35 DEBUG [org.jboss.resteasy.resteasy_jaxrs.i18n] RESTEASY002305: Failed executing POST /profile: javax.ws.rs.NotSupportedException: RESTEASY003065: Cannot consume content type
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:453)
at org.jboss.resteasy.core.registry.SegmentNode.match(SegmentNode.java:155)
at org.jboss.resteasy.core.registry.RootNode.match(RootNode.java:58)
at org.jboss.resteasy.core.registry.RootClassNode.match(RootClassNode.java:47)
at org.jboss.resteasy.core.ResourceMethodRegistry.getResourceInvoker(ResourceMethodRegistry.java:482)
at org.jboss.resteasy.core.SynchronousDispatcher.getInvoker(SynchronousDispatcher.java:323)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke(SynchronousDispatcher.java:244)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess[=15=](SynchronousDispatcher.java:153)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:30)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:63)
at io.undertow.servlet.handlers.ServletChain.handleRequest(ServletChain.java:68)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:133)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:65)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:270)
at io.undertow.servlet.handlers.ServletInitialHandler.access0(ServletInitialHandler.java:59)
at io.undertow.servlet.handlers.ServletInitialHandler.call(ServletInitialHandler.java:116)
at io.undertow.servlet.handlers.ServletInitialHandler.call(ServletInitialHandler.java:113)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction.call(ContextClassLoaderSetupAction.java:43)
at io.quarkus.undertow.runtime.UndertowDeploymentRecorder.call(UndertowDeploymentRecorder.java:476)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:250)
at io.undertow.servlet.handlers.ServletInitialHandler.access[=15=]0(ServletInitialHandler.java:59)
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:82)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:290)
at io.undertow.server.HttpServerExchange.run(HttpServerExchange.java:669)
at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:224)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at java.base/java.lang.Thread.run(Thread.java:830)
at org.jboss.threads.JBossThread.run(JBossThread.java:479)
我错过了什么吗?我在 Quarkus 站点上找到了 creating REST clients with multipart 的文档,但没有找到服务器端资源的文档。我还需要做些什么才能让它正常工作吗?
问题在于
@FormParam("profileImage")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
private byte[] profileImage = null;
您在宣传此部分允许的唯一内容类型是 application/octet-stream
。但是您正在发送图像,而 cURL 并未将图像的内容类型设置为该图像。我可以想象它在幕后将其设置为 image/jpg
。您可以手动设置类型(这将修复 this 请求)
profileImage=@profile_image.jpg;type=application/octet-stream
但就个人而言,我会删除 @PartType
。由于字段的类型已经是byte[]
,所以转换不会有问题。此外,许多客户无法设置各个部分的内容类型。就像 browser/JS 一样,客户端将默认设置文件的内容类型,就像 cURL 所做的那样。