Recommended way to save uploaded files in a servlet application

Posted in :

透過這篇文章,我們知道 request 的  context  和 serverlet context 是不同的層級。

 

Store it anywhere in an accessible location except of the IDE’s project folder aka the server’s deploy folder, for reasons mentioned in the answer to Uploaded image only available after refreshing the page:

  1. Changes in the IDE’s project folder does not immediately get reflected in the server’s work folder. There’s kind of a background job in the IDE which takes care that the server’s work folder get synced with last updates (this is in IDE terms called “publishing”). This is the main cause of the problem you’re seeing.
  2. In real world code there are circumstances where storing uploaded files in the webapp’s deploy folder will not work at all. Some servers do (either by default or by configuration) not expand the deployed WAR file into the local disk file system, but instead fully in the memory. You can’t create new files in the memory without basically editing the deployed WAR file and redeploying it.
  3. Even when the server expands the deployed WAR file into the local disk file system, all newly created files will get lost on a redeploy or even a simple restart, simply because those new files are not part of the original WAR file.

It really doesn’t matter to me or anyone else where exactly on the local disk file system it will be saved, as long as you do not ever use getRealPath() method. Using that method is in any case alarming.

The path to the storage location can in turn be definied in many ways. You have to do it all by yourself. Perhaps this is where your confusion is caused because you somehow expected that the server does that all automagically. Please note that @MultipartConfig(location) does not specify the final upload destination, but the temporary storage location for the case file size exceeds memory storage threshold.

So, the path to the final storage location can be definied in either of the following ways:

  • Hardcoded:
    File uploads = new File("/path/to/uploads");
  • Environment variable via SET UPLOAD_LOCATION=/path/to/uploads:
    File uploads = new File(System.getenv("UPLOAD_LOCATION"));
  • VM argument during server startup via -Dupload.location="/path/to/uploads":
    File uploads = new File(System.getProperty("upload.location"));
  • *.properties file entry as upload.location=/path/to/uploads:
    File uploads = new File(properties.getProperty("upload.location"));
  • web.xml <context-param> with name upload.location and value /path/to/uploads:
    File uploads = new File(getServletContext().getInitParameter("upload.location"));
  • If any, use the server-provided location, e.g. in JBoss AS/WildFly:
    File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");

Either way, you can easily reference and save the file as follows:

File file = new File(uploads, "somefilename.ext");

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath());
}

Or, when you want to autogenerate an unique file name to prevent users from overwriting existing files with coincidentally the same name:

File file = File.createTempFile("somefilename-", ".ext", uploads);

try (InputStream input = part.getInputStream()) {
    Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}

How to obtain part in JSP/Servlet is answered in How to upload files to server using JSP/Servlet?and how to obtain part in JSF is answered in How to upload file using JSF 2.2 <h:inputFile>? Where is the saved File?

Note: do not use Part#write() as it interprets the path relative to the temporary storage location defined in @MultipartConfig(location).

See also:


 

Introduction

The ServletContext#getRealPath() is intented to convert a web content path (the path in the expanded WAR folder structure on the server’s disk file system) to an absolute disk file system path.

The "/" represents the web content root. I.e. it represents the web folder as in the below project structure:

YourWebProject
 |-- src
 |    :
 |
 |-- web
 |    |-- META-INF
 |    |    `-- MANIFEST.MF
 |    |-- WEB-INF
 |    |    `-- web.xml
 |    |-- index.jsp
 |    `-- login.jsp
 :    

So, passing the "/" to getRealPath() would return you the absolute disk file system path of the /web folder of the expanded WAR file of the project. Something like /path/to/server/work/folder/some.war/ which you should be able to further use in File or FileInputStream.

Note that most starters don’t seem to see/realize that you can actually pass the whole web content path to it and that they often use

String absolutePathToIndexJSP = servletContext.getRealPath("/") + "index.jsp";

instead of

String absolutePathToIndexJSP = servletContext.getRealPath("/index.jsp");

Don’t ever write files in there

Also note that even though you can write new files into it using FileOutputStream, all changes (e.g. new files or edited files) will get lost whenever the WAR is redeployed; with the simple reason that all those changes are not contained in the original WAR file. So all starters who are attempting to save uploaded files in there are doing it wrong.

Moreover, getRealPath() will always return null or a completely unexpected path when the server isn’t configured to expand the WAR file into the disk file system, but instead into e.g. memory as a virtual file system.

getRealPath() is unportable; you’d better never use it

Use getRealPath() carefully. There are actually no sensible real world use cases for it. If all you actually need is to get an InputStream of the web resource, better use ServletContext#getResourceAsStream() instead, this will work regardless of the way how the WAR is expanded. So, if you for example want an InputStream of index.jsp, then do not do:

InputStream input = new FileInputStream(servletContext.getRealPath("/index.jsp")); // Wrong!

But instead do:

InputStream input = servletContext.getResourceAsStream("/index.jsp"); // Right!

Or if you intend to obtain a list of all available web resource paths, use ServletContext#getResourcePaths() instead.

Set<String> resourcePaths = servletContext.getResourcePaths("/");

You can obtain an individual resource as URL via ServletContext#getResource(). This will return null when the resource does not exist.

URL resource = servletContext.getResource(path);

Or if you intend to save an uploaded file, or create a temporary file, then see the below “See also” links.

See also:

 

 

from:

https://stackoverflow.com/questions/18664579/recommended-way-to-save-uploaded-files-in-a-servlet-application

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *