如何使用JSP/Servlet上传文件到服务器?

我试了一下:

<form action="upload" method="post">
    <input type="text" name="description" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

但是,我只得到了文件名,而不是文件内容。当我添加enctype="multipart/form-data"到<form>时,request.getParameter()返回null。

在研究过程中,我偶然发现了Apache Common FileUpload。我试了一下:

FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request); // This line is where it died.

不幸的是,servlet抛出了一个异常,没有明确的消息和原因。下面是stacktrace:

SEVERE: Servlet.service() for servlet UploadServlet threw exception
javax.servlet.ServletException: Servlet execution threw an exception
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:313)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Thread.java:637)

当前回答

如果你碰巧使用Spring MVC,这是如何(我把它留在这里,以防有人发现它有用):

使用一个enctype属性设置为"multipart/form-data"的表单(与BalusC的答案相同):

<form action="upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" value="Upload"/>
</form>

在控制器中,将请求参数文件映射为MultipartFile类型,如下所示:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public void handleUpload(@RequestParam("file") MultipartFile file) throws IOException {
    if (!file.isEmpty()) {
            byte[] bytes = file.getBytes(); // alternatively, file.getInputStream();
            // application logic
    }
}

你可以使用MultipartFile的getOriginalFilename()和getSize()来获取文件名和大小。

我已经用Spring版本4.1.1.RELEASE测试了这个功能。

其他回答

Use:

DiskFileUpload upload = new DiskFileUpload();

从这个对象中你必须获得文件项和字段,然后你可以像下面这样存储到服务器中:

String loc = "./webapps/prjct name/server folder/" + contentid + extension;
File uploadFile = new File(loc);
item.write(uploadFile);

您可以使用JSP /servlet上传文件。

<form action="UploadFileServlet" method="post">
    <input type="text" name="description" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

另一方面,在服务器端,使用以下代码。

package com.abc..servlet;

import java.io.File;
---------
--------


/**
 * Servlet implementation class UploadFileServlet
 */
public class UploadFileServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public UploadFileServlet() {
        super();
        // TODO Auto-generated constructor stub
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.sendRedirect("../jsp/ErrorPage.jsp");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        PrintWriter out = response.getWriter();
        HttpSession httpSession = request.getSession();
        String filePathUpload = (String) httpSession.getAttribute("path") != null ? httpSession.getAttribute("path").toString() : "" ;

        String path1 = filePathUpload;
        String filename = null;
        File path = null;
        FileItem item = null;


        boolean isMultipart = ServletFileUpload.isMultipartContent(request);

        if (isMultipart) {
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            String FieldName = "";
            try {
                List items = upload.parseRequest(request);
                Iterator iterator = items.iterator();
                while (iterator.hasNext()) {
                     item = (FileItem) iterator.next();

                        if (fieldname.equals("description")) {
                            description = item.getString();
                        }
                    }
                    if (!item.isFormField()) {
                        filename = item.getName();
                        path = new File(path1 + File.separator);
                        if (!path.exists()) {
                            boolean status = path.mkdirs();
                        }
                        /* Start of code fro privilege */

                        File uploadedFile = new File(path + Filename);  // for copy file
                        item.write(uploadedFile);
                        }
                    } else {
                        f1 = item.getName();
                    }

                } // END OF WHILE
                response.sendRedirect("welcome.jsp");
            } catch (FileUploadException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

介绍

要浏览并选择要上传的文件,您需要表单中的HTML <input type="file">字段。在HTML规范中,你必须使用POST方法,并且表单的enctype属性必须设置为“multipart/form-data”。

<form action="upload" method="post" enctype="multipart/form-data">
    <input type="text" name="description" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

在提交这样的表单之后,请求体中的二进制多部分表单数据与未设置enctype时的格式不同。

在Servlet 3.0(2009年12月)之前,Servlet API不支持多部分/表单数据。它只支持application/x-www-form-urlencoded的默认表单封装类型。当使用多部分表单数据时,request.getParameter()和consorts都将返回null。这就是著名的Apache Commons FileUpload出现的地方。

不要手动解析!

You can in theory parse the request body yourself based on ServletRequest#getInputStream(). However, this is a precise and tedious work which requires precise knowledge of RFC2388. You shouldn't try to do this on your own or copypaste some homegrown library-less code found elsewhere on the Internet. Many online sources have failed hard in this, such as roseindia.net. See also uploading of pdf file. You should rather use a real library which is used (and implicitly tested!) by millions of users for years. Such a library has proven its robustness.

当您已经使用Servlet 3.0或更新版本时,请使用本机API

如果你至少在使用Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3,等等,他们早在2010年就已经存在了),那么你可以使用标准API提供的HttpServletRequest#getPart()来收集单独的多部分表单数据项(大多数Servlet 3.0实现实际上使用Apache Commons FileUpload来实现这一点!)此外,普通表单字段可以通过getParameter()以通常的方式获得。

首先用@MultipartConfig注释你的servlet,让它识别和支持多部分/表单数据请求,从而让getPart()工作:

@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    // ...
}

然后,实现它的doPost(),如下所示:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String description = request.getParameter("description"); // Retrieves <input type="text" name="description">
    Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file">
    String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
    InputStream fileContent = filePart.getInputStream();
    // ... (do your job here)
}

注意路径#getFileName()。这是MSIE对获取文件名的修复。此浏览器错误地沿文件名发送完整的文件路径,而不是仅发送文件名。

如果你想通过multiple="true"上传多个文件,

<input type="file" name="files" multiple="true" />

或者是传统的多输入方式,

<input type="file" name="files" />
<input type="file" name="files" />
<input type="file" name="files" />
...

然后你可以按照下面的方法收集它们(不幸的是没有request.getParts("files")这样的方法):

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // ...
    List<Part> fileParts = request.getParts().stream().filter(part -> "files".equals(part.getName()) && part.getSize() > 0).collect(Collectors.toList()); // Retrieves <input type="file" name="files" multiple="true">

    for (Part filePart : fileParts) {
        String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
        InputStream fileContent = filePart.getInputStream();
        // ... (do your job here)
    }
}

当您还没有使用Servlet 3.1时,手动获取提交的文件名

请注意,#getSubmittedFileName()部分是在Servlet 3.1中引入的(Tomcat 8, Jetty 9, WildFly 8, GlassFish 4等,它们从2013年就已经存在了)。如果您还没有使用Servlet 3.1(真的吗?),那么您需要一个额外的实用工具方法来获取提交的文件名。

private static String getSubmittedFileName(Part part) {
    for (String cd : part.getHeader("content-disposition").split(";")) {
        if (cd.trim().startsWith("filename")) {
            String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
            return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1); // MSIE fix.
        }
    }
    return null;
}
String fileName = getSubmittedFileName(filePart);

请注意MSIE在获取文件名方面的修复。此浏览器错误地沿文件名发送完整的文件路径,而不是仅发送文件名。

如果您还没有使用Servlet 3.0,请使用Apache Commons FileUpload

If you're not on Servlet 3.0 yet (isn't it about time to upgrade? it's released over a decade ago!), the common practice is to make use of Apache Commons FileUpload to parse the multpart form data requests. It has an excellent User Guide and FAQ (carefully go through both). There's also the O'Reilly ("cos") MultipartRequest, but it has some (minor) bugs and isn't actively maintained anymore for years. I wouldn't recommend using it. Apache Commons FileUpload is still actively maintained and currently very mature.

为了使用Apache Commons FileUpload,你需要在你的webapp的/WEB-INF/lib中至少有以下文件:

commons-fileupload.jar commons-io.jar

您最初的尝试失败很可能是因为您忘记了公共IO。

下面是一个启动示例,当使用Apache Commons FileUpload时,你的UploadServlet的doPost()可能看起来像:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
        for (FileItem item : items) {
            if (item.isFormField()) {
                // Process regular form field (input type="text|radio|checkbox|etc", select, etc).
                String fieldName = item.getFieldName();
                String fieldValue = item.getString();
                // ... (do your job here)
            } else {
                // Process form file field (input type="file").
                String fieldName = item.getFieldName();
                String fileName = FilenameUtils.getName(item.getName());
                InputStream fileContent = item.getInputStream();
                // ... (do your job here)
            }
        }
    } catch (FileUploadException e) {
        throw new ServletException("Cannot parse multipart request.", e);
    }

    // ...
}

非常重要的一点是,不要预先在同一个请求上调用getParameter()、getParameterMap()、getParameterValues()、getInputStream()、getReader()等。否则servlet容器将读取并解析请求体,因此Apache Commons FileUpload将得到一个空请求体。请参见a.o. ServletFileUpload#parseRequest(request)返回一个空列表。

注意FilenameUtils#getName()。这是MSIE对获取文件名的修复。此浏览器错误地沿文件名发送完整的文件路径,而不是仅发送文件名。

或者,你也可以把这些都包装在一个Filter中,它会自动解析,并把这些东西放回请求的参数映射中,这样你就可以继续以通常的方式使用request. getparameter(),并通过request. getattribute()检索上传的文件。你可以在这篇博客文章中找到一个例子。

解决GlassFish3 getParameter()仍然返回null的bug

注意,Glassfish 3.1.2以上的版本有一个bug,其中getParameter()仍然返回null。如果你的目标是这样一个容器,并且不能升级它,那么你需要在这个实用工具方法的帮助下从getPart()中提取值:

private static String getValue(Part part) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(part.getInputStream(), "UTF-8"));
    StringBuilder value = new StringBuilder();
    char[] buffer = new char[1024];
    for (int length = 0; (length = reader.read(buffer)) > 0;) {
        value.append(buffer, 0, length);
    }
    return value.toString();
}
String description = getValue(request.getPart("description")); // Retrieves <input type="text" name="description">
    

保存上传的文件(不要使用getRealPath()或part.write()!)

关于正确保存获得的InputStream(上面代码片段中显示的fileContent变量)到磁盘或数据库的详细信息,请参阅以下答案:

在servlet应用程序中保存上传文件的推荐方法 如何上传图片并保存在数据库中? 如何将部分转换为Blob,这样我就可以将它存储在MySQL?

服务上传文件

有关如何将保存的文件从磁盘或数据库返回到客户端的详细信息,请参阅以下回答:

使用<h:graphicImage>或<img>标签从webapps / webcontext / deploy文件夹外部加载图像 如何在JSP页面中检索和显示数据库中的图像? 在Java web应用程序中提供来自应用程序服务器外部的静态数据的最简单方法 支持HTTP缓存的静态资源servlet抽象模板

ajax化的形式

下面是如何使用Ajax(和jQuery)上传的答案。请注意,不需要为此更改用于收集表单数据的servlet代码!只有响应的方式可能会改变,但这相当简单(例如,不转发到JSP,只打印一些JSON或XML,甚至纯文本,这取决于负责Ajax调用的脚本所期望的内容)。

如何使用JSP/Servlet和Ajax将文件上传到服务器? 通过XMLHttpRequest以多部分形式发送文件 HTML5拖放文件上传到Java Servlet


希望这一切都有帮助:)

如果您使用Geronimo及其嵌入式Tomcat,则会出现此问题的另一个来源。在本例中,经过多次测试Commons IO和Commons -fileupload后,问题产生于处理Commons -xxx JAR文件的父类加载器。这种情况必须加以防止。坠机通常发生在:

fileItems = uploader.parseRequest(request);

请注意,fileItems的List类型已经随着common -fileupload的当前版本而改变,具体为List<FileItem>,而不是以前的版本,它是一般的List。

I added the source code for commons-fileupload and Commons IO into my Eclipse project to trace the actual error and finally got some insight. First, the exception thrown is of type Throwable not the stated FileIOException nor even Exception (these will not be trapped). Second, the error message is obfuscatory in that it stated class not found because axis2 could not find commons-io. Axis2 is not used in my project at all, but it exists as a folder in the Geronimo repository subdirectory as part of standard installation.

最后,我找到了一个地方,提出了一个有效的解决方案,成功地解决了我的问题。您必须将JAR文件隐藏在部署计划中的父加载器中。它被放入geronimo-web.xml文件中,我的完整文件如下所示。

粘贴自http://osdir.com/ml/user-geronimo-apache/2011-03/msg00026.html:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web:web-app xmlns:app="http://geronimo.apache.org/xml/ns/j2ee/application-2.0" xmlns:client="http://geronimo.apache.org/xml/ns/j2ee/application-client-2.0" xmlns:conn="http://geronimo.apache.org/xml/ns/j2ee/connector-1.2" xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2" xmlns:ejb="http://openejb.apache.org/xml/ns/openejb-jar-2.2" xmlns:log="http://geronimo.apache.org/xml/ns/loginconfig-2.0" xmlns:name="http://geronimo.apache.org/xml/ns/naming-1.2" xmlns:pers="http://java.sun.com/xml/ns/persistence" xmlns:pkgen="http://openejb.apache.org/xml/ns/pkgen-2.1" xmlns:sec="http://geronimo.apache.org/xml/ns/security-2.0" xmlns:web="http://geronimo.apache.org/xml/ns/j2ee/web-2.0.1">
    <dep:environment>
        <dep:moduleId>
            <dep:groupId>DataStar</dep:groupId>
            <dep:artifactId>DataStar</dep:artifactId>
            <dep:version>1.0</dep:version>
            <dep:type>car</dep:type>
        </dep:moduleId>

        <!-- Don't load commons-io or fileupload from parent classloaders -->
        <dep:hidden-classes>
            <dep:filter>org.apache.commons.io</dep:filter>
            <dep:filter>org.apache.commons.fileupload</dep:filter>
        </dep:hidden-classes>
        <dep:inverse-classloading/>

    </dep:environment>
    <web:context-root>/DataStar</web:context-root>
</web:web-app>

您需要将common-io.1.4.jar文件包含在lib目录中,或者如果您在任何编辑器中工作,例如NetBeans,那么您需要转到项目属性并添加JAR文件,然后就完成了。

要获得common.io.jar文件,只需谷歌它,或者只需访问Apache Tomcat网站,在那里您可以获得免费下载该文件的选项。但请记住一件事:如果您是Windows用户,请下载二进制ZIP文件。