Как загрузить файлы на сервер, используя JSP / Servlet?

Как насчет:

import copy
d = { ... }
d2 = copy.deepcopy(d)

Python 2 или 3:

Python 3.2 (r32:88445, Feb 20 2011, 21:30:00) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import copy
>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = copy.deepcopy(my_dict)
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
3
>>>
665
задан Taryn 22 March 2017 в 16:13
поделиться

1 ответ

Введение

Для просмотра и выбора файла для загрузки вам необходимо поле HTML в форме. Как указано в спецификации 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 Servlet API не поддерживал multipart/form-data. Он поддерживал только стандартный enctype формы application/x-www-form-urlencoded. request.getParameter() и его аналоги возвращают null при использовании данных формы multipart. Именно здесь на помощь приходит хорошо известный Apache Commons FileUpload.

Не разбирайте его вручную!

Теоретически вы можете самостоятельно разобрать тело запроса на основе ServletRequest#getInputStream(). Однако это точная и утомительная работа, требующая точного знания RFC2388. Не стоит пытаться сделать это самостоятельно или копировать код без библиотеки, найденный где-либо в Интернете. Многие онлайновые источники потерпели в этом неудачу, например roseindia.net. См. также загрузка файла pdf. Вам лучше использовать настоящую библиотеку, которую используют (и неявно тестируют!) миллионы пользователей в течение многих лет. Такая библиотека доказала свою надежность.

Если вы уже используете Servlet 3.0 или новее, используйте родной API

Если вы используете как минимум Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3 и т.д.), то вы можете просто использовать стандартный API, предоставляемый HttpServletRequest#getPart() для сбора отдельных элементов данных многочастной формы (большинство реализаций Servlet 3.0 фактически используют Apache Commons FileUpload под прикрытием для этого!). Также обычные поля формы доступны с помощью getParameter() обычным способом.

Сначала аннотируйте ваш сервлет с @MultipartConfig для того, чтобы он распознавал и поддерживал multipart/form-data запросы и таким образом заставил работать 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)
}

Обратите внимание на Path#getFileName(). Это исправление MSIE для получения имени файла. Этот браузер неправильно отправляет полный путь к файлу вместе с именем вместо только имени файла.

В случае если у вас есть для многофайловой загрузки, соберите их как показано ниже (к сожалению, нет такого метода как request. getParts("file")):

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // ...
    List<Part> fileParts = request.getParts().stream().filter(part -> "file".equals(part.getName())).collect(Collectors.toList()); // Retrieves <input type="file" name="file" 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, вручную получите имя отправленного файла

Обратите внимание, что Part#getSubmittedFileName() был введен в Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4 и т.д.). Если вы еще не на 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

Если вы еще не на Servlet 3.0 (не пора ли перейти на новую версию?), общепринятой практикой является использование Apache Commons FileUpload для разбора запросов данных многокомпонентных форм. У него есть отличное Руководство пользователя и FAQ (внимательно изучите оба). Существует также MultipartRequest от O'Reilly ("cos"), но он имеет некоторые (незначительные) ошибки и уже несколько лет активно не поддерживается. Я бы не рекомендовал его использовать. Apache Commons FileUpload все еще активно поддерживается и в настоящее время очень развит.

Для того чтобы использовать Apache Commons FileUpload, вам необходимо иметь в /WEB-INF/lib вашего webapp по крайней мере следующие файлы:

Ваша первая попытка не удалась, скорее всего, потому что вы забыли commons IO.

Вот пример того, как может выглядеть doPost() вашего UploadServlet при использовании Apache Commons FileUpload:

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() и т.д. в одном запросе заранее. В противном случае контейнер сервлетов прочитает и разберет тело запроса, и таким образом Apache Commons FileUpload получит пустое тело запроса. См. также a.o. ServletFileUpload#parseRequest(request) возвращает пустой список.

Обратите внимание на FilenameUtils#getName(). Это исправление MSIE для получения имени файла. Этот браузер неправильно отправляет полный путь к файлу вместе с именем вместо только имени файла.

В качестве альтернативы вы также можете обернуть все это в Filter, который автоматически разберет все это и поместит материал обратно в параметрическую карту запроса, чтобы вы могли продолжить использовать request.getParameter() обычным способом и получить загруженный файл с помощью request.getAttribute(). Пример можно найти в этой статье блога.

Решение проблемы для GlassFish3, когда getParameter() все еще возвращает null

Обратите внимание, что версии Glassfish старше 3.1.2 имели ошибку, когда 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, как показано в приведенных выше фрагментах кода) на диск или в базу данных:

Обслуживание загруженного файла

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

Ajaxifying the form

Перейдите к следующим ответам, как загружать изображения с помощью Ajax (и jQuery). Обратите внимание, что код сервлета для сбора данных формы для этого менять не нужно! Можно изменить только способ ответа, но это довольно тривиально (т.е. вместо пересылки в JSP просто выведите JSON или XML или даже простой текст, в зависимости от того, что ожидает скрипт, отвечающий за вызов Ajax).


Надеюсь, это все поможет :)

1159
ответ дан 22 November 2019 в 21:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: