Введение
Для просмотра и выбора файла для загрузки вам необходимо поле HTML <input type="file">
в форме. Как указано в спецификации 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()
и consorts будут возвращать 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())).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 (внимательно изучите оба). Есть также 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).
Надеюсь, что все это помогает:)