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


121

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

  • Яке найкраще місце для збереження файлу? Чи потрібно зберегти його /WEB-INF/uploads( тут не рекомендується ) або в іншому місці $CATALINA_BASE(див. Тут ) або ...? Підручник JavaEE 6 отримує шлях від користувача (: wtf :). Примітка: файл не можна завантажувати будь-якими способами.

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

  • Мене зацікавило б викладення недоліків цього підходу щодо бази даних / сховища JCR

На жаль, FileServlet від @BalusC зосереджується на завантаженні файлів, тоді як його відповідь на завантаження файлів пропускає частину, куди потрібно зберегти файл.

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


Для мого останнього способу його виконання див. Відповідь нижче
Mr_and_Mrs_D

Відповіді:


165

Зберігайте його в будь-якому місці у доступному місці, за винятком папки проекту IDE, яка називається папкою розгортання сервера, з причин, зазначених у відповіді на завантажене зображення, доступному лише після оновлення сторінки :

  1. Зміни в папці проекту IDE не одразу відображаються у робочій папці сервера. В IDE є якесь фонове завдання, яке дбає про те, щоб робоча папка сервера синхронізувалася з останніми оновленнями (це в термінах IDE називається "публікація"). Це основна причина проблеми, яку ви бачите.

  2. У реальному коді світу є обставини, коли зберігання завантажених файлів у папці розгортання webapp взагалі не працюватиме. Деякі сервери (за замовчуванням чи за конфігурацією) не розгортають розгорнутий файл WAR в файлову систему локального диска, а натомість повністю в пам'яті. Ви не можете створити нові файли в пам'яті без редагування розгорнутого файлу WAR та його повторної розгортання.

  3. Навіть коли сервер розгортає розгорнутий файл WAR у файлову систему локального диска, всі новостворені файли втрачаються за допомогою повторної розробки або навіть простого перезавантаження, просто тому, що ці нові файли не є частиною вихідного файлу WAR.

Мені і комусь насправді не важливо, де саме в локальній дисковій файловій системі він буде збережений, доки ви ніколи не використовуєте getRealPath()метод . Використання цього методу в будь-якому випадку насторожує.

Шлях до місця зберігання в свою чергу може бути визначений багатьма способами. Ви повинні зробити це все самостійно . Можливо, саме тут і виникає ваша плутанина, тому що ви якось очікували, що сервер це зробить автоматично. Зверніть увагу, що @MultipartConfig(location)це не так вказати кінцевий пункт призначення завантаження, але місце тимчасового зберігання розміру разі файлу перевищує поріг зберігання пам'яті.

Отже, шлях до остаточного місця зберігання можна визначити одним із наступних способів:

  • Жорсткий код:

      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).

Дивитися також:


У @MultipartConfig(location)Визначає тимчасове розташування сторгується , який сервер повинен використовувати , якщо розмір файлу перевищує поріг для зберігання пам'яті, а не постійною місце зберігання , де ви б в кінцевому рахунку , як він буде зберігатися. Це значення за замовчуванням до шляху, визначеного java.io.tmpdirвластивістю системи. Дивіться також цей пов'язаний відповідь на невдалу спробу JSF: stackoverflow.com/questions/18478154 / ...
BalusC

1
Дякую - сподіваюсь, я не звучу ідіотів, але ця цитата Part.write>> Це дозволяє певній реалізації використовувати, наприклад, перейменування файлів, де це можливо, а не копіювати всі базові дані, отримуючи тим самим значну перевагу від продуктивності у поєднанні з деякими невідомий "вирізати" (проти копіювати) метод, скажімо, якийсь апарат apache lib врятував би мені клопоту написати сам байт - і відтворити файл уже там (див. також тут )
Mr_and_Mrs_D

Так, якщо ви вже користуєтесь сервлетом 3.0, ви можете скористатися цим Part#write(). Я оновив відповідь.
BalusC

Дякую вам за те, що ви поновлювали публікацію - чи є така власність для Tomcat "jboss.server.data.dir"?
Mr_and_Mrs_D

1
Ні, його немає.
BalusC

7

Я розміщую свій остаточний спосіб зробити це на основі прийнятої відповіді:

@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, і якщо ви знайшли помилку, дайте мені знати


1
Що робити, якщо я хочу незалежне рішення, яке працює в обох випадках (win / ux)? Чи потрібно встановлювати інший шлях upload.location або є якийсь інший підказку?
пікімота
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.