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

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

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

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

  • Мне было бы интересно разоблачить недостатки этого подхода в сравнении с репозиторием базы данных /JCR

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

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

Ответы

Ответ 1

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

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

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

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

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

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

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

  • Запрограммированный:

    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 < h: inputFile > ? Где сохраненный файл?

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

См. также:

Ответ 2

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

@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://stackoverflow.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, и если вы найдете ошибку, сообщите мне