Программирование HTML Apache: использование FileUpload Wed, February 08 2023  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Apache: использование FileUpload Печать
Добавил(а) microsin   

FileUpload может использоваться многими способами, в зависимости от требований Вашего приложения. В самом простом случае вызывается один метод для парсинга запроса сервлета, и затем обрабатывается список элементов по мере их применения к Вашему приложению. В более сложном случае можно настроить FileUpload для получения полного контроля над каждым сохраняемым элементом. Например, можно выбрать потоковую передачу контента в базу данных.

В этой документации (перевод руководства [1]) будут рассмотрены основные принципы работы FileUpload, и проиллюстрированы некоторые простые, наиболее общие методы его применения.

FileUpload зависит от Commons IO, поэтому убедитесь, что в classpath у Вас версия, упомянутая на страничке зависимостей [2].

[Как работает FileUpload]

Запрос выгрузки файлов состоит из упорядоченного списка элементов, которые закодированы в соответствии с RFC 1867, "Form-based File Upload in HTML". FileUpload может выполнить парсинг такого запроса, и предоставить Вашему приложению список отдельных выгруженных элементов. Каждый такой элемент реализует интерфейс FileItem, независимо от его нижележащей реализации.

На этой страничке описано традиционное API библиотеки выгрузки файлов. Это API удобно для использования, однако для достижения максимальной производительности Вы можете предпочесть выбор более быстрого Streaming API [3].

Каждый элемент файла имеет несколько свойств, которые могут быть интересны Вашему приложению. Например, у каждого такого элемента есть имя (name) и тип содержимого (content type), а также элемент может предоставить InputStream для доступа к своим данным. С другой стороны, Вам возможно потребуется обработать каждый элемент по-разному, в зависимости от того, является ли элемено полем обычной формы - т. е. данные были получены из обычного текст-бокса или подобного поля HTML, или это был выгруженный файл. Интерфейс FileItem предоставляет методы определения таких способов выгрузки, и обеспечения доступа к данным наиболее подходящим способом.

FileUpload создает новые элементы файлов с использованием FileItemFactory, именно это дает большую часть гибкости в использовании. У FileItemFactory имеется полное управление над каждым созданным элементом. Реализация FileItemFactory, которая в настоящее время поставляется вместе с FileUpload, сохраняет данные элемента в память или на диск, в зависимости от размера элемента (т. е. количества его байт данных). Однако это поведение может быть настроено в соответствии с требованиями приложения.

Начиная с версии 1.1, FileUpload поддерживает запросы выгрузки файла в обоих окружениях servlet и portlet. В этих двух окружениях способы использования почти идентичны, поэтому остальная часть этого описания относится только к сервлетам.

Если Вы собираете portlet-приложение, то ниже приведены 2 отличия, которые следует учитывать при чтении дальнейшего изложения:

• Там, где Вы видите ссылки на класс ServletFileUpload, сделайте замену на класс PortletFileUpload.
• Там, где Вы видите ссылки на класс HttpServletRequest, сделайте замену на класс ActionRequest.

[Парсинг запроса]

Разумеется, что перед тем, как можно будет работать с выгруженными элементами, сначала надо обработать сам запрос. FileUpload предоставляет для этого статический метод.

// Проверка, что у нас запрос на выгрузку файла (file upload request):
boolean isMultipart = ServletFileUpload.isMultipartContent(request);

Теперь мы готовы парсить запрос для разбора его на составные элементы.

Самый простой случай. Предположим следующее:

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

Для этого случая обработка запроса будет очень простой:

// Создание фактории для элементов файла, сохраняемых на диск:
DiskFileItemFactory factory = new DiskFileItemFactory();
 
// Конфигурирование репозитория (чтобы обеспечить безопасность использования временного
// размещения):
ServletContext servletContext = this.getServletConfig().getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);
 
// Создание нового обработчика выгрузки файла:
ServletFileUpload upload = new ServletFileUpload(factory);
 
// Парсинг запроса:
List< FileItem> items = upload.parseRequest(request);

Результатом парсинга будет список файловых элементов, каждый из которых реализует интерфейс FileItem. Обработка этих элементов будет показана далее.

Получение дополнительного управления. Если Ваш сценарий использования близок к простому, который показан выше, но нужно получить немного больше контроля, то можно настроить поведение обработчика выгрузки, или элемента FileItemFactory, или их обоих. Следующий пример показывает несколько опций конфигурации:

// Создание factory для элементов файла, основанных на диске:
DiskFileItemFactory factory = new DiskFileItemFactory();
 
// Установка ограничений factory:
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
 
// Создается новый обработчик выгрузки файла:
ServletFileUpload upload = new ServletFileUpload(factory);
 
// Установка ограничения на общий размер запроса:
upload.setSizeMax(yourMaxRequestSize);
 
// Парсинг запроса:
List< FileItem> items = upload.parseRequest(request);

Конечно, каждый из методов конфигурации не зависит от других, однако если Вы хотите сконфигурировать factory сразу, то можно сделать это с помощью альтернативного конструктора, примерно так:

// Создание factory для элементов файла, основанных на диске:
DiskFileItemFactory factory = new DiskFileItemFactory(yourMaxMemorySize, yourTempDirectory);

Если требуется дополнительный контроль над парсингом запроса, например, сохранение элементов в другом месте (например, в базе данных), то необходимо обратиться к настройке FileUpload.

[Обработка выгруженных элементов]

Как только парсинг был завершен, Вы получите список (List) элементов файлов, которые надо будет обработать. В большинстве случаев Вы заходите обработать выгрузки файлов отдельно от стандартных полей формы, тогда обработка списка может выглядеть примерно так:

// Обработка выгруженных элементов:
Iterator< FileItem> iter = items.iterator();
while (iter.hasNext()){
   FileItem item = iter.next();
   if (item.isFormField()) {
      processFormField(item);
   } else {
       processUploadedFile(item);
   }
}

Для обычного поля формы скорее всего Вас будет интересовать только имя элемента, и его значение строки (String).

// Обработка обычного поля формы:
if (item.isFormField()) {
   String name = item.getFieldName();
   String value = item.getString();
   ...
}

Для выгрузки файла есть несколько разных вещей, которые надо знать перед тем, как выполнить обработку контента. Ниже приведен пример некоторых методов, которые могут Вас заинтересовать.

// Обработка выгрузки файла.
if (!item.isFormField()) {
   String fieldName = item.getFieldName();
   String fileName = item.getName();
   String contentType = item.getContentType();
   boolean isInMemory = item.isInMemory();
   long sizeInBytes = item.getSize();
   ...
}

С выгруженными файлами доступ к ним через память обычно не требуется, если они достаточно большие по размеру, или если нет другой альтернативы. Вместо этого Вы захотите обработать контент как поток, или записать весь файл в его конечное расположение. FileUpload предоставляет простые средства для выполнения обоих этих действий.

// Обработка выгрузки файла.
if (writeToFile) {
   File uploadedFile = new File(...);
   item.write(uploadedFile);
}
else {
   InputStream uploadedStream = item.getInputStream();
   ...
   uploadedStream.close();
}

Имейте в виду, что в реализации по умолчанию FileUpload функция write() будет пытаться переименовать файл, чтобы он попал в указанное место, если данные уже находятся во временном файле. Реальное копирование данных файла делается только если переименование по какой-то причине потерпело неудачу, или если данные были в оперативной памяти.

Если нужно обращаться к выгруженным данным в памяти, то нужно просто вызвать метод get(), чтобы получить данные как массив байт.

// Обработка файла, данные которого находятся в памяти:
byte[] data = item.get();
...

[Очистка ресурсов]

Описание в этой секции применимо только если Вы используете DiskFileItem. Другими словами, это применимо, если выгруженные файлы записываются во временные файлы перед их обработкой.

Такие временные файлы удаляются автоматически, если они больше не используются (точнее, если соответствующий экземпляр DiskFileItem обработан сборщиком мусора). Это делается в фоновом режиме классом org.apache.commons.io.FileCleanerTracker, который запускает поток чистки (reaper thread).

Этот reaper thread должен быть остановлен, если он больше не нужен. В рабочем окружении сервлета это делается специальным прослушивателем контекста сервлетов, который называется FileCleanerCleanup. Для этой цели добавьте в свой web.xml секцию наподобие следующей:

< web-app>
  ...
  < listener>
    < listener-class>
      org.apache.commons.fileupload.servlet.FileCleanerCleanup
    < /listener-class>
  < /listener>
  ...
< /web-app>

Создание DiskFileItemFactory. FileCleanerCleanup предоставляет экземпляр org.apache.commons.io.FileCleaningTracker. Этот экземпляр должен использоваться, когда создается org.apache.commons.fileupload.disk.DiskFileItemFactory. Это должно быть сделано вызовом метода наподобие следующего:

public static DiskFileItemFactory newDiskFileItemFactory(ServletContext context,
                                                         File repository){
   FileCleaningTracker fileCleaningTracker
        = FileCleanerCleanup.getFileCleaningTracker(context);
   DiskFileItemFactory factory
        = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,
                                  repository);
   factory.setFileCleaningTracker(fileCleaningTracker);
   return factory;
}

Запрет очистки временных файлов. Чтобы запретить отслеживание временных файлов, Вы можете установить FileCleaningTracker в null. Как следствие, созданные файлы больше не будут отслеживаться. В частности, они больше не будут автоматически удаляться.

[Взаимодействие с антивирусами]

Сканеры вирусов, работающие на той же операционной системе, что и веб-контейнер, могут привести к нежелательному поведению для приложений, использующих FileUpload. В этой секции описаны некоторые варианты такого поведения, и  даны некоторые идеи по поводу их обработки.

Реализация по умолчанию FileUpload работает так, что элементы размером свыше определенного порога будут записаны на диск. Как только такой файл закроется, любой сканер вирусов в системе проснется и проверит его, и потенциально может поместить на карантин. В результате файл может быть перемещен в специальное карантинное хранилище. Это конечно будет сюрпризом для разработчика приложения, поскольку выгруженный элемент файла больше не будет доступен для обработки. С другой стороны, выгруженные элементы размером ниже порога будут удерживаться в памяти, и сканер вирусов его не увидит. Это позволяет сохранить вирус в какой-либо форм (хотя если он когда-нибудь будет записан на диск, сканер вирусов найдет его и проведет инспекцию).

Одним из часто используемых решений проблемы является выделение в системе одного каталога, в который будут помещены все загруженные файлы, и настройка антивирусного сканера на игнорирование этого каталога. Это гарантирует, что файлы не будут вырваны из-под приложения, но при этом оставляет ответственность за поиск вирусов на разработчике приложения. Сканирование загруженных файлов на наличие вирусов может быть выполнено внешним процессом, который может переместить чистые или очищенные файлы в "утвержденное" место, или путем интеграции антивирусного сканера в само приложение.

[Отслеживание прогресса]

Если вы ожидаете выгрузки по-настоящему большого файла, то было бы неплохо сообщать об этом процессе пользователю. Даже страницы HTML позволяют реализовать индикатор прогресса путем возврата ответа multipart/replace, или чего-то подобного.

Наблюдение за прогрессом выгрузки реализуется через progress listener:

// Создание progress listener:
ProgressListener progressListener = new ProgressListener(){
   public void update(long pBytesRead, long pContentLength, int pItems) {
       System.out.println("We are currently reading item " + pItems);
       if (pContentLength == -1) {
           System.out.println("So far, " + pBytesRead + " bytes have been read.");
       } else {
           System.out.println("So far, " + pBytesRead + " of " + pContentLength
                              + " bytes have been read.");
       }
   }
};
upload.setProgressListener(progressListener);

Попробуйте реализовать показанный выше код, и увидите его недостаток: progress listener вызывается слишком часто, и излишне нагружает систему. В зависимости от подсистемы сервлета и другого рабочего окружения он может даже вызываться для каждого сетевого пакета! Это создает реальную проблему производительности. Типовое решение - снизить активность listener. Например, можно выдавать сообщение прогресса только если поменяется количество мегабайт:

// Создание progress listener:
ProgressListener progressListener = new ProgressListener(){
   private long megaBytes = -1;
   public void update(long pBytesRead, long pContentLength, int pItems) {
       long mBytes = pBytesRead / 1000000;
       if (megaBytes == mBytes) {
           return;
       }
       megaBytes = mBytes;
       System.out.println("We are currently reading item " + pItems);
       if (pContentLength == -1) {
           System.out.println("So far, " + pBytesRead + " bytes have been read.");
       }
       else {
           System.out.println("So far, " + pBytesRead + " of " + pContentLength
                              + " bytes have been read.");
       }
   }
};

[Ссылки]

1. Apache Using FileUpload site:apache.org.
2. Apache Project Dependencies site:apache.org.
3. Apache Streaming API site:apache.org.

 

Добавить комментарий


Защитный код
Обновить

Top of Page