【问题标题】:How to implement FileUpload in embedded Jetty?如何在嵌入式 Jetty 中实现 FileUpload?
【发布时间】:2013-07-15 10:52:56
【问题描述】:

我正在编写一个包含网络服务器的 android 应用程序。因此我使用 Embedded Jetty (8.0.1)。

我要做的下一步是实现文件上传。

HTML 看起来像这样,我认为是正确的:

<form action=\"fileupload\" method=\"post\" enctype=\"multipart/form-data\"> 
    <table>
        <tbody>
            <tr>
                <td><input type=\"file\" name=\"userfile1\" /></td>
            </tr>
            <tr>
                <td>
                     <input type=\"submit\" value=\"Submit\" /><input type=\"reset\" value=\"Reset\" />
                </td>
            </tr>
        </tbody>
    </table>
</form>

当我使用这个表单时,我可以在 logcat 中看到我收到了一个文件,但我无法在我的 Servlet 中访问这个文件。

我试过了

File file1 = (File) request.getAttribute( "userfile1" );

并具有以下功能:

request.getParameter()

但每次我收到一个 NULL 对象。我该怎么办?

【问题讨论】:

    标签: file-upload embedded-jetty


    【解决方案1】:

    由于这是一个多部分请求,并且您正在上传“文件”部分,因此您需要使用

    request.getPart("userfile1");
    

    对于您的请求中不属于“文件”类型的元素,例如 input type="text",您可以通过 request.getParameter 访问这些属性。

    请注意,Jetty 8.0.1 已经很老了,最新的 Jetty 版本(在撰写本文时为 8.1.12)包含一些重要的多部分处理错误修复。

    如果您升级 Jetty 版本,您可能必须显式启用 Multipart 处理,因为 Jetty 更严格地执行 Servlet 3.0 规范 (https://bugs.eclipse.org/bugs/show_bug.cgi?id=395000),如果您正在使用 servlet,请使用 @MultipartConfig 注释。

    对于处理程序,您必须在调用 getPart(s) 之前手动将 Request.__MULTIPART_CONFIG_ELEMENT 作为属性添加到您的请求中

    if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data")) {
      baseRequest.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
    }
    

    这将允许您解析多部分请求,但是如果您以这种方式注入配置,则不会清除创建的临时多部分文件。对于 servlet,它由附加到请求的 ServletRequestListener 处理(请参阅 org.eclipse.jetty.server.Request#MultiPartCleanerListener)。

    所以,我们要做的是在处理程序链的早期有一个 HandlerWrapper,如果需要,它会添加多部分配置,并确保在请求完成后清理多部分文件。示例:

    import java.io.IOException;
    
    import javax.servlet.MultipartConfigElement;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.eclipse.jetty.http.HttpMethod;
    import org.eclipse.jetty.server.Request;
    import org.eclipse.jetty.server.handler.HandlerWrapper;
    import org.eclipse.jetty.util.MultiException;
    import org.eclipse.jetty.util.MultiPartInputStreamParser;
    
    /**
     * Handler that adds the multipart config to the request that passes through if
     * it is a multipart request.
     *
     * <p>
     * Jetty will only clean up the temp files generated by
     * {@link MultiPartInputStreamParser} in a servlet event callback when the
     * request is about to die but won't invoke it for a non-servlet (webapp)
     * handled request.
     *
     * <p>
     * MultipartConfigInjectionHandler ensures that the parts are deleted after the
     * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
     * method is called.
     *
     * <p>
     * Ensure that no other handlers sit above this handler which may wish to do
     * something with the multipart parts, as the saved parts will be deleted on the return
     * from
     * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}.
     */
    public class MultipartConfigInjectionHandler extends HandlerWrapper {
      public static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data";
    
      private static final MultipartConfigElement MULTI_PART_CONFIG = new MultipartConfigElement(
          System.getProperty("java.io.tmpdir"));
    
      public static boolean isMultipartRequest(ServletRequest request) {
        return request.getContentType() != null
            && request.getContentType().startsWith(MULTIPART_FORMDATA_TYPE);
      }
    
      /**
       * If you want to have multipart support in your handler, call this method each time
       * your doHandle method is called (prior to calling getParameter).
       *
       * Servlet 3.0 include support for Multipart data with its
       * {@link HttpServletRequest#getPart(String)} & {@link HttpServletRequest#getParts()}
       * methods, but the spec says that before you can use getPart, you must have specified a
       * {@link MultipartConfigElement} for the Servlet.
       *
       * <p>
       * This is normally done through the use of the MultipartConfig annotation of the
       * servlet in question, however these annotations will not work when specified on
       * Handlers.
       *
       * <p>
       * The workaround for enabling Multipart support in handlers is to define the
       * MultipartConfig attribute for the request which in turn will be read out in the
       * getPart method.
       *
       * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=395000#c0">Jetty Bug
       *      tracker - Jetty annotation scanning problem (servlet workaround) </a>
       * @see <a href="http://dev.eclipse.org/mhonarc/lists/jetty-users/msg03294.html">Jetty
       *      users mailing list post.</a>
       */
      public static void enableMultipartSupport(HttpServletRequest request) {
        request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
      }
    
      @Override
      public void handle(String target, Request baseRequest, HttpServletRequest request,
          HttpServletResponse response) throws IOException, ServletException {
        boolean multipartRequest = HttpMethod.POST.is(request.getMethod())
            && isMultipartRequest(request);
        if (multipartRequest) {
          enableMultipartSupport(request);
        }
    
        try {
          super.handle(target, baseRequest, request, response);
        } finally {
          if (multipartRequest) {
            MultiPartInputStreamParser multipartInputStream = (MultiPartInputStreamParser) request
                .getAttribute(Request.__MULTIPART_INPUT_STREAM);
            if (multipartInputStream != null) {
              try {
                // a multipart request to a servlet will have the parts cleaned up correctly, but
                // the repeated call to deleteParts() here will safely do nothing.
                multipartInputStream.deleteParts();
              } catch (MultiException e) {
    //            LOG.error("Error while deleting multipart request parts", e);
              }
            }
          }
        }
      }
    }
    

    这可以像这样使用:

    MultipartConfigInjectionHandler multipartConfigInjectionHandler =
        new MultipartConfigInjectionHandler();
    
    HandlerCollection collection = new HandlerCollection();
    collection.addHandler(new SomeHandler());
    collection.addHandler(new SomeOtherHandler());
    
    multipartConfigInjectionHandler.setHandler(collection);
    
    server.setHandler(multipartConfigInjectionHandler);
    

    【讨论】:

    • 这个回复对我很有帮助。对 eclipse 错误的引用很有帮助,但这篇文章更有用,因为它提供了更多详细信息:dev.eclipse.org/mhonarc/lists/jetty-users/msg03294.html
    • 该消息列表评论实际上是我的评论之一 :) 我在这里更新了答案,提供了一些关于清理多部分临时文件的附加信息。
    • @Andrew 你能发布一个使用嵌入式码头上传的最小工作示例吗?我不敢相信这已经是 6 年的问题了。让我对码头完全失去兴趣
    【解决方案2】:

    MultiPartInputStreamParser 类自码头 9.4.11 起已弃用 被MultiPartFormInputStream取代了

    新代码如下所示:

    import java.io.IOException;
    
    import javax.servlet.MultipartConfigElement;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.eclipse.jetty.http.HttpMethod;
    import org.eclipse.jetty.http.MultiPartFormInputStream;
    import org.eclipse.jetty.server.Request;
    import org.eclipse.jetty.server.handler.HandlerWrapper;
    import org.eclipse.jetty.util.MultiPartInputStreamParser;
    
    /**
     * Handler that adds the multipart config to the request that passes through if
     * it is a multipart request.
     *
     * <p>
     * Jetty will only clean up the temp files generated by
     * {@link MultiPartInputStreamParser} in a servlet event callback when the
     * request is about to die but won't invoke it for a non-servlet (webapp)
     * handled request.
     *
     * <p>
     * MultipartConfigInjectionHandler ensures that the parts are deleted after the
     * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
     * method is called.
     *
     * <p>
     * Ensure that no other handlers sit above this handler which may wish to do
     * something with the multipart parts, as the saved parts will be deleted on the return
     * from
     * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}.
     */
    public class MultipartConfigInjectionHandler extends HandlerWrapper {
        public static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data";
    
        private static final MultipartConfigElement MULTI_PART_CONFIG = new MultipartConfigElement(
                        System.getProperty("java.io.tmpdir"));
    
        public static boolean isMultipartRequest(ServletRequest request) {
            return request.getContentType() != null
                            && request.getContentType().startsWith(MULTIPART_FORMDATA_TYPE);
        }
    
        /**
         * If you want to have multipart support in your handler, call this method each time
         * your doHandle method is called (prior to calling getParameter).
         *
         * Servlet 3.0 include support for Multipart data with its
         * {@link HttpServletRequest#getPart(String)} & {@link HttpServletRequest#getParts()}
         * methods, but the spec says that before you can use getPart, you must have specified a
         * {@link MultipartConfigElement} for the Servlet.
         *
         * <p>
         * This is normally done through the use of the MultipartConfig annotation of the
         * servlet in question, however these annotations will not work when specified on
         * Handlers.
         *
         * <p>
         * The workaround for enabling Multipart support in handlers is to define the
         * MultipartConfig attribute for the request which in turn will be read out in the
         * getPart method.
         *
         * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=395000#c0">Jetty Bug
         *      tracker - Jetty annotation scanning problem (servlet workaround) </a>
         * @see <a href="http://dev.eclipse.org/mhonarc/lists/jetty-users/msg03294.html">Jetty
         *      users mailing list post.</a>
         */
        public static void enableMultipartSupport(HttpServletRequest request) {
            request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, MULTI_PART_CONFIG);
        }
    
        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request,
                        HttpServletResponse response) throws IOException, ServletException {
            boolean multipartRequest = HttpMethod.POST.is(request.getMethod())
                            && isMultipartRequest(request);
            if (multipartRequest) {
                enableMultipartSupport(request);
            }
    
            try {
                super.handle(target, baseRequest, request, response);
            } finally {
                if (multipartRequest) {
                    String MULTIPART = "org.eclipse.jetty.servlet.MultiPartFile.multiPartInputStream";
                    MultiPartFormInputStream multipartInputStream = (MultiPartFormInputStream) request.getAttribute( MULTIPART );
                    if (multipartInputStream != null) {
                        multipartInputStream.deleteParts();
                    }
                }
            }
        }
    }
    

    还有来自 Jetty 作者的official example

    【讨论】:

    • MultiPartFormInputStream 返回 null。但我设法得到了MultiParts.MultiPartsUtilParser multiparts=(MultiParts.MultiPartsUtilParser)request.getAttribute(Request.__MULTIPARTS); 然后我用它来访问零件和delete 他们。for(Part p: multiparts.getParts()){p.delete();}
    • 感谢 MultiPartFormInputStream 的提示,升级到 jetty-11 表明这是要走的路。
    猜你喜欢
    • 2018-11-22
    • 2012-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    • 1970-01-01
    相关资源
    最近更新 更多