Рекомендуемый способ сохранения загруженных файлов в приложении сервлета

Я прочитал здесь, что не следует сохранять файл на сервере, так как он не переносимый, транзакционный и требует внешних параметров. Однако, учитывая, что мне нужно решение tmp для tomcat (7) и что у меня есть (относительный) контроль над сервером, который я хочу знать:

  • Какое лучшее место для сохранения файла? Должен ли я сохранить его в /WEB-INF/uploads (рекомендуется здесь) или где-то под $CATALINA_BASE (см. здесь) или...? Учебник по JavaEE 6 получает путь от пользователя (:wtf:). NB. Файл не должен быть загружен никакими средствами.

  • Должен ли я настроить параметр конфигурации, как описано здесь? Я был бы признателен за некоторый код (я бы предпочел дать ему относительный путь - так что он, по крайней мере, переносим Tomcat) - Part.write() выглядит многообещающе - но, видимо, нужен абсолютный путь

  • Я был бы заинтересован в изложении недостатков этого подхода по сравнению с базой данных / хранилищем JCR.

К сожалению, FileServlet @BalusC концентрируется на загрузке файлов, в то время как его ответ на загрузку файлов пропускает часть о том, где сохранить файл.

Было бы предпочтительнее решение, легко конвертируемое для использования DB или реализации JCR (например, jackrabbit).

2 ответа

Решение

Храните его в любом месте в доступном месте, кроме папки проекта IDE, то есть папки развертывания сервера, по причинам, указанным в ответе на Загруженное изображение, доступном только после обновления страницы:

  1. Изменения в папке проекта среды IDE не сразу отражаются в рабочей папке сервера. В IDE есть своего рода фоновое задание, которое заботится о том, чтобы рабочая папка сервера синхронизировалась с последними обновлениями (это в терминах IDE, называемых "публикацией"). Это основная причина проблемы, которую вы видите.

  2. В реальном мире кода существуют обстоятельства, когда сохранение загруженных файлов в папке развертывания веб-приложения не будет работать вообще. Некоторые серверы (по умолчанию или по конфигурации) не раскрывают развернутый файл WAR в файловую систему локального диска, а полностью в памяти. Вы не можете создавать новые файлы в памяти, не редактируя развернутый WAR-файл и не перераспределяя его.

  3. Даже если сервер развернет развернутый файл WAR в файловой системе локального диска, все вновь созданные файлы будут потеряны при повторном развертывании или даже при простом перезапуске просто потому, что эти новые файлы не являются частью исходного файла WAR.

Мне или кому-либо еще не важно, где именно на файловой системе локального диска он будет сохранен, если вы никогда не используете его. getRealPath() метод. Использование этого метода в любом случае вызывает тревогу.

Путь к месту хранения, в свою очередь, может быть определен разными способами. Вы должны сделать все это самостоятельно. Возможно, в этом причина вашей путаницы, потому что вы почему-то ожидали, что сервер сделает все это автоматически. Обратите внимание, что @MultipartConfig(location) не указывает конечное место загрузки, но место временного хранения для файла дела превышает порог памяти.

Таким образом, путь к конечному месту хранения может быть определен одним из следующих способов:

  • HARDCODED:

    File uploads = new File("/path/to/uploads");
    
  • Переменная среды через SET UPLOAD_LOCATION=/path/to/uploads:

    File uploads = new File(System.getenv("UPLOAD_LOCATION"));
    
  • Аргумент VM во время запуска сервера через -Dupload.location="/path/to/uploads":

    File uploads = new File(System.getProperty("upload.location"));
    
  • *.properties запись в файле как upload.location=/path/to/uploads:

    File uploads = new File(properties.getProperty("upload.location"));
    
  • web.xml<context-param> с именем upload.location и значение /path/to/uploads:

    File uploads = new File(getServletContext().getInitParameter("upload.location"));
    
  • Если есть, используйте предоставленное сервером расположение, например, в JBoss AS / WildFly:

    File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");
    

В любом случае, вы можете легко ссылаться и сохранять файл следующим образом:

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

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

Или, если вы хотите автоматически сгенерировать уникальное имя файла, чтобы пользователи не могли перезаписывать существующие файлы с одинаковым именем:

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

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

Как получить part в JSP/Servlet ответ в Как загрузить файлы на сервер с помощью JSP/Servlet? и как получить part в JSF ответили в Как загрузить файл, используя JSF 2.2 ? Где находится сохраненный файл?

Примечание: не используйте Part#write() поскольку он интерпретирует путь относительно места временного хранения, определенного в @MultipartConfig(location),

Смотрите также:

Я публикую свой последний способ сделать это на основе принятого ответа:

@SuppressWarnings("serial")
@WebServlet("/")
@MultipartConfig
public final class DataCollectionServlet extends Controller {

    private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
    private String uploadsDirName;

    @Override
    public void init() throws ServletException {
        super.init();
        uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // ...
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Collection<Part> parts = req.getParts();
        for (Part part : parts) {
            File save = new File(uploadsDirName, getFilename(part) + "_"
                + System.currentTimeMillis());
            final String absolutePath = save.getAbsolutePath();
            log.debug(absolutePath);
            part.write(absolutePath);
            sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
        }
    }

    // helpers
    private static String getFilename(Part part) {
        // courtesy of BalusC : http://stackru.com/a/2424824/281545
        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;
    }
}

где:

@SuppressWarnings("serial")
class Controller extends HttpServlet {

    static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
    static ServletContext sc;
    Logger log;
    // private
    // "/WEB-INF/app.properties" also works...
    private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
    private Properties properties;

    @Override
    public void init() throws ServletException {
        super.init();
        // synchronize !
        if (sc == null) sc = getServletContext();
        log = LoggerFactory.getLogger(this.getClass());
        try {
            loadProperties();
        } catch (IOException e) {
            throw new RuntimeException("Can't load properties file", e);
        }
    }

    private void loadProperties() throws IOException {
        try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
                if (is == null)
                    throw new RuntimeException("Can't locate properties file");
                properties = new Properties();
                properties.load(is);
        }
    }

    String property(final String key) {
        return properties.getProperty(key);
    }
}

и /WEB-INF/app.properties:

upload.location=C:/_/

HTH и если вы найдете ошибку, дайте мне знать

Другие вопросы по тегам