Введение
Для просмотра и выбора файла для загрузки вам нужно <input type="file">
поле 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 API Servlet изначально не поддерживался multipart/form-data
. Он поддерживает только тип формы по умолчанию application/x-www-form-urlencoded
. Все request.getParameter()
операторы and возвращаются null
при использовании данных многоэлементной формы. Вот тут-то и появилась хорошо известная 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 для получения имени файла. Этот браузер неправильно отправляет полный путь к файлу вдоль имени, а не только имя файла.
Если у вас есть возможность <input type="file" name="file" multiple="true" />
загрузки нескольких файлов, соберите их, как показано ниже (к сожалению, такого метода нет 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()) && part.getSize() > 0).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 для анализа запросов данных из нескольких частей. Он имеет отличное руководство пользователя и часто задаваемые вопросы (внимательно просмотрите оба). Есть также O'Reilly (" cos ") MultipartRequest
, но он имеет некоторые (незначительные) ошибки и больше не поддерживается в течение многих лет. Я не рекомендовал бы использовать это. Apache Commons FileUpload все еще активно поддерживается и в настоящее время очень зрелый.
Чтобы использовать Apache Commons FileUpload, в вашем веб-приложении должны быть как минимум следующие файлы /WEB-INF/lib
:
Ваша первоначальная попытка, скорее всего, не удалась, потому что вы забыли общий ввод-вывод.
Вот пример того, как 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 получит пустое тело запроса. Смотрите также. ServletFileUpload # parseRequest (запрос) возвращает пустой список .
Обратите внимание 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
переменная, как показано в приведенных выше фрагментах кода) на диск или в базу данных:
Обслуживание загруженного файла
Обратитесь к следующим ответам для получения подробной информации о правильной доставке сохраненного файла с диска или базы данных обратно клиенту:
Аяксификация формы
Перейдите к следующим ответам, как загрузить с помощью Ajax (и jQuery). Обратите внимание, что код сервлета для сбора данных формы не нужно менять для этого! Может быть изменено только то, как вы отвечаете, но это довольно тривиально (т.е. вместо пересылки в JSP просто напечатайте JSON или XML или даже простой текст в зависимости от того, что ожидает скрипт, ответственный за вызов Ajax).
Надеюсь, это все помогает :)