可以在部分内找不到Content-Disposition标头

时间:2016-08-03 15:31:31

标签: android angularjs java-ee wildfly resteasy

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

我对Content-Disposition的理解是它应该被定义为每个上传部分的标题,但也可以在请求标题中定义 - 如果只上传了一个文件。

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

应该可以从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");
}

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

@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);
}

上传图片的代码(Android):

@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();
}

如上所述,我正在用双连字符,边界和换行符开始“每个”部分。由于标题是由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 的WARN消息所以我通过在警告模块中添加WFLYWELD0052: (...) Package-private access will not work.core作为依赖项来解决这些问题,如suggested in this issue。它删除了警告消息,但没有解决问题。

编辑:

以下是Android应用程序上传请求的日志,我打印随请求发送的标题:

spi

通过Postman上传时,我可以按预期访问终点 - 无需添加超过文件(可能Postman会自动创建内容处理标签?)。

编辑2:

我更新了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. - 评论)。它实际上是一个导致错误行为的日志语句。我将添加一个答案来解释原因!

1 个答案:

答案 0 :(得分:2)

失败原因:

发生这种情况的原因是我添加的日志语句,以验证发送到服务器的标头。在我原来的问题中,这被省略了,因为以下内容最初是从另一个方法中提取的。当我从这个方法复制片段时,我没有想到日志语句与问题相关。

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,调用方法只是吞下了异常并打印出来了#34;无法上传图片;服务器响应406"。这种糟糕的异常处理使我过分关注服务器导致问题。

实际发生了什么?

我在Android应用程序中遗漏的异常消息是cannot write request body after response has been read,它让我查看代码的每一行,直到我发现log语句试图读取标题。

为什么这会导致错误?

我不是100%确定这个的实际原因,但我可以想象从连接读取告诉服务器;

" 我已经完成了我想对你说的话,现在我想知道你要对我说什么"。

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

再一次;我对这一点并不是100%肯定,因为我还没看过。

结论:

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