如何使用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)

当前回答

介绍

要浏览并选择要上传的文件,您需要表单中的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


希望这一切都有帮助:)

其他回答

我正在为每个HTML表单使用一个公共Servlet,无论它是否有附件。

这个Servlet返回一个TreeMap,其中键是JSP名称参数,值是用户输入,并将所有附件保存在一个固定的目录中,稍后您可以根据自己的选择重命名目录。这里Connections是具有连接对象的自定义接口。

public class ServletCommonfunctions extends HttpServlet implements
        Connections {

    private static final long serialVersionUID = 1L;

    public ServletCommonfunctions() {}

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException,
                          IOException {}

    public SortedMap<String, String> savefilesindirectory(
            HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        // Map<String, String> key_values = Collections.synchronizedMap(new
        // TreeMap<String, String>());
        SortedMap<String, String> key_values = new TreeMap<String, String>();
        String dist = null, fact = null;
        PrintWriter out = response.getWriter();
        File file;
        String filePath = "E:\\FSPATH1\\2KL06CS048\\";
        System.out.println("Directory Created   ????????????"
            + new File(filePath).mkdir());
        int maxFileSize = 5000 * 1024;
        int maxMemSize = 5000 * 1024;

        // Verify the content type
        String contentType = request.getContentType();
        if ((contentType.indexOf("multipart/form-data") >= 0)) {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            // Maximum size that will be stored in memory
            factory.setSizeThreshold(maxMemSize);
            // Location to save data that is larger than maxMemSize.
            factory.setRepository(new File(filePath));
            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload(factory);
            // maximum file size to be uploaded.
            upload.setSizeMax(maxFileSize);
            try {
                // Parse the request to get file items.
                @SuppressWarnings("unchecked")
                List<FileItem> fileItems = upload.parseRequest(request);
                // Process the uploaded file items
                Iterator<FileItem> i = fileItems.iterator();
                while (i.hasNext()) {
                    FileItem fi = (FileItem) i.next();
                    if (!fi.isFormField()) {
                        // Get the uploaded file parameters
                        String fileName = fi.getName();
                        // Write the file
                        if (fileName.lastIndexOf("\\") >= 0) {
                            file = new File(filePath
                                + fileName.substring(fileName
                                        .lastIndexOf("\\")));
                        } else {
                            file = new File(filePath
                                + fileName.substring(fileName
                                        .lastIndexOf("\\") + 1));
                        }
                        fi.write(file);
                    } else {
                        key_values.put(fi.getFieldName(), fi.getString());
                    }
                }
            } catch (Exception ex) {
                System.out.println(ex);
            }
        }
        return key_values;
    }
}

Use:

DiskFileUpload upload = new DiskFileUpload();

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

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

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

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

首先你必须将表单的enctype属性设置为"multipart/form-data"

如下所示。

<form action="Controller" method="post" enctype="multipart/form-data">
     <label class="file-upload"> Click here to upload an Image </label>
     <input type="file" name="file" id="file" required>
</form>

然后,在Servlet“控制器”中添加用于多部分的注释,以指示在Servlet中处理多部分数据。

完成此操作后,检索通过表单发送的部分,然后检索提交文件的文件名(带路径)。使用它在所需的路径中创建一个新文件,并将文件的部分写入新创建的文件以重新创建该文件。

如下图所示:

@MultipartConfig

public class Controller extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        insertImage(request, response);
    }

    private void addProduct(HttpServletRequest request, HttpServletResponse response) {
        Part filePart = request.getPart("file");
        String imageName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();

        String imageSavePath = "specify image path to save image"; //path to save image
        FileOutputStream outputStream = null;
        InputStream fileContent = null;

        try {
            outputStream = new FileOutputStream(new File(imageSavePath + File.separator + imageName));
            // Creating a new file with file path and the file name
            fileContent = filePart.getInputStream();
            // Getting the input stream
            int readBytes = 0;
            byte[] readArray = new byte[1024];
            // Initializing a byte array with size 1024

            while ((readBytes = fileContent.read(readArray)) != -1) {
                outputStream.write(readArray, 0, readBytes);
            } // This loop will write the contents of the byte array unitl the end to the output stream
        } catch (Exception ex) {
            System.out.println("Error Writing File: " + ex);
        } finally {
            if (outputStream != null) {
                outputStream.close();
                // Closing the output stream
            }
            if (fileContent != null) {
                fileContent.close();
                // Closing the input stream
            }
        }
    }
}

如果您使用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>