【问题标题】:Could find no Content-Disposition header within part在部件中找不到 Content-Disposition 标头
【发布时间】:2016-12-09 10:18:59
【问题描述】:

我正在尝试将图片文件从我的 Android 应用程序上传到我的 JavaEE REST 服务,该服务已部署到 JBoss Wildfly 9 服务器。

我对@9​​87654322@的理解是,应该为上传的每一部分定义自己的header,但也可以在request header中定义——如果只上传一个文件的话。

所以在标题中我定义了Content-Type: multipart/form-data;boundary=*****,而每个(现在只有一个)部分以--***** 开头,然后我以--*****-- 结束请求正文。这是为了以防我稍后需要升级到多个文件上传。

应该可以从 Android 和 AngularJS 应用程序访问服务器。因此,我为 AngularJS 应用程序添加了 ContainerResponseFilter,如下所示,但我看不出有任何理由这应该是阻止请求的原因。

@Override
public void filter(ContainerRequestContext requestCtx, ContainerResponseContext responseCtx)
        throws IOException {
    responseCtx.getHeaders().add("Access-Control-Allow-Origin", "http://localhost:8000");
    responseCtx.getHeaders().add("Access-Control-Allow-Credentials", "true");
    responseCtx.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
    responseCtx.getHeaders().add("Access-Control-Allow-Headers", "Host, "
        + "Accept, "
        + "Origin, "
        + "Connection, "
        + "Content-Type, "
        + "Cache-Control, "
        + "Content-Length, "
        + "Accept-Encoding, "
        + "Content-Disposition");
}

@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
    String reqSource = "(" + servletRequest.getRemoteAddr() + ") "
            + servletRequest.getRemoteUser() + "@"
            + servletRequest.getRemoteHost() + ":"
            + servletRequest.getRemotePort();
    LOGGER.trace(" :: Source :: [{}]", reqSource);

    String userId = (securityContext.getUserPrincipal() != null ?
            securityContext.getUserPrincipal().getName() : "unknown");
    LOGGER.trace(" :: User :: [{}]", userId);

    String reqUri = servletRequest.getRequestURI();
    String reqType = servletRequest.getMethod();
    LOGGER.trace(" :: Boundary :: [{}] - [{}]", reqUri, reqType);
}

这些过滤器是我添加的唯一与应用程序和端点之间的请求和响应交互的代码。我也尝试删除这些过滤器,但没有任何运气。删除 responseFilter 会中断与 AngularJS 应用程序的通信,而删除 requestFilter 只会停止日志记录。

@POST
@Consumes({MULTIPART_FORM_DATA})
public Response createPicture(MultipartFormDataInput input) {

    for (InputPart inputPart : input.getParts()) {
        try {
            fileController.saveFile(inputPart);

            return Response.ok().build();
        } catch (FileNotSavedException e) {
            return Response.serverError(e.getMessage()).build();
        }
    }

    return badRequestNullResponse();
}

图片上传代码(Android):

public static void uploadBitmap(Bitmap bitmap, String filename) 
        throws IOException {
    URL url = new URL(URL_REST_API + FILE);
    HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
    httpUrlConnection.setUseCaches(false);
    httpUrlConnection.setDoOutput(true);
    httpUrlConnection.setDoInput(true);

    httpUrlConnection.setRequestMethod("POST");
    httpUrlConnection.setRequestProperty("Connection", "Keep-Alive");
    httpUrlConnection.setRequestProperty("Cache-Control", "no-cache");
    httpUrlConnection.setRequestProperty("Accept", "multipart/form-data");
    httpUrlConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);

    // EDIT 2: This following log statement was omitted in the first post.
    // I extracted the setRequestProperty statements from another method
    // due readability of this question, but I had missed to copy this:
    Log.e(LOG_TAG, "Headers: \n" + httpUrlConnection.getHeaderFields());

    DataOutputStream request = new DataOutputStream(httpUrlConnection.getOutputStream());

    // Part [start]
    request.writeBytes(DOUBLE_HYPHEN + BOUNDARY + CR_LF);
    request.writeBytes("Content-Disposition: form-data;filename=\"" + filename + "\"" + CR_LF);
    request.writeBytes(CR_LF);

    // Part [content]
    bitmap.compress(Bitmap.CompressFormat.JPEG, IMAGE_QUALITY_PERCENTAGE, request);

    // part [end]
    request.writeBytes(CR_LF);
    request.writeBytes(DOUBLE_HYPHEN + BOUNDARY + DOUBLE_HYPHEN + CR_LF);

    request.flush();
    request.close();

    httpUrlConnection.disconnect();
}

如前所述,我以双连字符、边界和换行符开始“每个”部分。由于标头是由setRequestProperty 创建的,我假设标头已正确结束。那么,在这种情况下,它是由遗体丢失引起的吗?为什么Content-Disposition 或图片文件没有写入请求?


07:55:28,479 WARN  [org.apache.james.mime4j.parser.MimeEntity] (default task-36) Unexpected end of headers detected. Higher level boundary detected or EOF reached.
07:55:28,479 WARN  [org.apache.james.mime4j.parser.MimeEntity] (default task-36) Invalid header encountered
07:55:28,479 WARN  [org.apache.james.mime4j.parser.MimeEntity] (default task-36) Body part ended prematurely. Boundary detected in header or EOF reached.
07:55:28,479 WARN  [org.jboss.resteasy.core.ExceptionHandler] (default task-36) Failed executing POST /file: org.jboss.resteasy.spi.ReaderException: java.lang.RuntimeException: Could find no Content-Disposition header within part
    at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:183)
    at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:89)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:112)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:296)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:250)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:237)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:86)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
    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:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:72)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    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:282)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:261)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:80)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:172)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:774)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: Could find no Content-Disposition header within part
    at org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInputImpl.extractPart(MultipartFormDataInputImpl.java:68)
    at org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl.extractParts(MultipartInputImpl.java:229)
    at org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl.parse(MultipartInputImpl.java:198)
    at org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataReader.readFrom(MultipartFormDataReader.java:52)
    at org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataReader.readFrom(MultipartFormDataReader.java:20)
    at org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataReader$Proxy$_$$_WeldClientProxy.readFrom(Unknown Source)
    at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.readFrom(AbstractReaderInterceptorContext.java:59)
    at org.jboss.resteasy.core.interception.ServerReaderInterceptorContext.readFrom(ServerReaderInterceptorContext.java:62)
    at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.proceed(AbstractReaderInterceptorContext.java:51)
    at org.jboss.resteasy.security.doseta.DigitalVerificationInterceptor.aroundReadFrom(DigitalVerificationInterceptor.java:32)
    at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.proceed(AbstractReaderInterceptorContext.java:53)
    at org.jboss.resteasy.plugins.interceptors.encoding.GZIPDecodingInterceptor.aroundReadFrom(GZIPDecodingInterceptor.java:59)
    at org.jboss.resteasy.plugins.interceptors.encoding.GZIPDecodingInterceptor$Proxy$_$$_WeldClientProxy.aroundReadFrom(Unknown Source)
    at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.proceed(AbstractReaderInterceptorContext.java:53)
    at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:150)
    ... 38 more

还有;我发现了一些关于WFLYWELD0052: (...) Package-private access will not work. 的WARN 消息,所以我通过在警告模块中添加corespi 作为依赖项来修复这些问题,如suggested in this issue。它删除了警告消息,但没有解决问题。

编辑:

这是来自 android 应用程序的上传请求的日志,我正在打印随请求发送的标头:

(default task-42) Header: [Accept]
(default task-42) Value: [multipart/form-data]
(default task-42) Header: [Cache-Control]
(default task-42) Value: [no-cache]
(default task-42) Header: [Connection]
(default task-42) Value: [Keep-Alive]
(default task-42) Header: [User-Agent]
(default task-42) Value: [Dalvik/2.1.0 (Linux; U; Android 5.0.2; SM-A500FU Build/LRX22G)]
(default task-42) Header: [Host]
(default task-42) Value: [***.**.***.***:8080]
(default task-42) Header: [Accept-Encoding]
(default task-42) Value: [gzip]
(default task-42) Header: [Content-Length]
(default task-42) Value: [0]
(default task-42) Header: [Content-Type]
(default task-42) Value: [multipart/form-data;boundary=*****]
(default task-42)  :: Source :: [(*.***.**.**) null@*.***.**.**:28686]
(default task-42)  :: User :: [unknown]
(default task-42)  :: Boundary :: [/upload/api/file/] - [POST]
(default task-42) Unexpected end of headers detected. Higher level boundary detected or EOF reached.
(default task-42) Invalid header encountered
(default task-42) Body part ended prematurely. Boundary detected in header or EOF reached.

当通过 Postman 上传时,我能够按预期访问端点 - 无需添加超过文件(也许 Postman 会自动创建内容处置标签?)。

编辑 2:

我已更新 Android 代码,原因是错误(请参阅Edit 2-comment)。这实际上是导致错误行为的日志语句。我会添加一个答案来解释原因!

【问题讨论】:

    标签: android angularjs jakarta-ee wildfly resteasy


    【解决方案1】:

    失败原因:

    发生这种情况的原因是我添加了一条日志语句,以验证发送到服务器的标头。在我最初的问题中,这被省略了,因为以下内容最初是从另一种方法中提取的。当我从该方法中复制 sn-p 时,我认为日志语句与问题无关。

    httpUrlConnection.setUseCaches(false);
    httpUrlConnection.setDoOutput(true);
    httpUrlConnection.setDoInput(true);
    
    httpUrlConnection.setRequestMethod("POST");
    httpUrlConnection.setRequestProperty("Connection", "Keep-Alive");
    httpUrlConnection.setRequestProperty("Cache-Control", "no-cache");
    httpUrlConnection.setRequestProperty("Accept", "multipart/form-data");
    httpUrlConnection.setRequestProperty("Content-Type", 
        "multipart/form-data;boundary=" + BOUNDARY);
    

    以下行也在提取的方法中,导致问题。

    Log.e(LOG_TAG, "Headers: \n" + httpUrlConnection.getHeaderFields());
    

    说明:

    为什么您之前没有注意到这一点?

    uploadBitmap 方法只是 throws IOException 和调用者方法只是吞下了异常并打印出“无法上传图片;服务器响应 406”。这种糟糕的异常处理让我过分关注服务器导致的问题。

    究竟发生了什么?

    我在 Android 应用程序中错过的异常消息是 cannot write request body after response has been read,这让我查看了每一行代码,直到我发现日志语句试图读取标头。

    为什么会导致错误?

    我不能 100% 确定造成这种情况的实际原因,但我可以想象从连接中读取数据会告诉服务器;

    我想对你说的都说完了,现在我想知道你要对我说什么”。

    因此,当我在写入标头后从连接中读取数据时,似乎我基本上是在发送请求以获取响应。这随后导致服务器找不到Content-Disposition 的错误,因为服务器从来没有任何内容处置、部件甚至正文要处理。

    再一次;我对此不是 100% 确定的,因为我还没有查到它。

    结论:

    好吧,在这个错误上浪费了太多时间之后,我发现我应该更加注意我的日志语句。我也应该停止使用日志语句来获取我可以通过简单、简单的调试轻松获得的值。

    【讨论】:

      猜你喜欢
      • 2011-02-02
      • 2012-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-30
      • 1970-01-01
      相关资源
      最近更新 更多