Заметки про java и загрузку файлов с помощью commons fileupload

February 4, 2008

Введение



Сегодня я расскажу о том, как выполнять загрузку файлов из браузера в ваш сервлет или jsp-файл. Не секрет, что встроенной поддержки загрузки файлов в j2ee нет. Иногда по форумам кочуют самодельные скрипты парсинга http-запроса (несколько лет назад я и сам баловался написанием анализатора http-запросов), но то время прошло и теперь все большее количество java-разработчиков открывают для себя Commons FileUpload.

Напомню, что для того, чтобы html-форма могла отправить файл, необходимо указать для нее метод отправки “method” равный “post” (ну не в адресной же строке передавать содержимое файла…). А также необходимо указать способ кодирования отправляемой информации равным “multipart/form-data” (есть два алгоритма кодирования информации перед отправкой по сети: “multipart/form-data” и “x-www-form-urlencoded”). Для доступа к обычным (текстовым) полям формы мы используем либо вызовы:
  1. String variableValue = request.getParameter(variableName);
Либо jstl синтаксис:
  1. <c:out value="${para.userFio}" />
Проблема в том, что так добраться до присланных из формы полей можно лишь в случае, если данные были присланы методом “x-www-form-urlencoded”. В случае же метода “multipart/form-data”, мы не можем добраться не только до полей с файлами, но и даже до обычных текстовых полей. Особенность парсинга входящих запросов в java в том, что первое же обращение к присланным веб-приложению параметрам с помощью getParameter приведет к потере “сырых” данных и другие приложения не смогут выполнить повторный парсинг данных (с учетом каких-либо особенностей входного потока). Например, если вы работаете со struts (который сам выполняет парсинг mutipart-запросов), то использование commons fileupload вызывает ряд сложностей. Но хватит слов, давайте напишем немного кода:

Немножко кода



Стадия настройки: в методе init сервлет-а необходимо создать фабрику DiskFileItemFactory. Ее назначение – управлять параметрами обработки запроса. Дело в том, что все входящие переменные (как обычные, так и файловые) сохраняются либо в памяти либо на жестком диске сервера в зависимости от их размера. Откровенно говоря, мне не нравится такой подход, т.к. почти всегда данные пришедшие из текстовых полей приходится помещать в базу данных и эти данные действительно должны находиться в памяти. А вот ситуации с обработкой пришедших файлов (например, изменение размера картинки или конвертация видео-файла являются достаточно редкими) обычно файл просто переносится в определенную папку на сервера. А в базу данных заносится имя этого файла. Возвращаясь к вопросу создания фабрики, вы должны указать в качестве параметров конструктора число (играющее роль порога для принятия решения: сохранять ли входную переменную на диск или в файл), вторым же параметром при создании DiskFileItemFactory выступает путь к каталогу, где будут сохраняться временные файлы. Если вы эти значения не укажите, то в качестве пороговой величины будет принята отметка в 10240 байт, а для каталога с файлами используется значение возвращаемой системной переменной:
  1. System.getProperty("java.io.tmpdir").
Для обеспечения автоматического удаления временных файлов вы можете запустить специальный сервис FileCleanerCleanup. Этот сервлет-слушатель, запускает специальный сервис отслеживания ненужных файлов и уничтожения их, как только все ссылки на подобный файл будут уничтожены (надо сказать, что в документации написано, что поддержка удаления файлов будет выполнена автоматически при выполнении сборки мусора – но оставлять этот вопрос на самотек не желательно). Для регистрации “сборщика мусора” нужно добавить в файл web.xml следующие строки:
  1. <listener>
  2.     <listener-class>
  3.       org.apache.commons.fileupload.servlet.FileCleanerCleanup
  4.     </listener-class>
  5.   </listener>
После создания фабрики DiskFileItemFactory вы создаете объект, непосредственно отвечающий за процесс парсинга входящего запроса. Делать это нужно в зависимости от того, в какой среде выполняется ваше приложение, если это обычный сервлет или jsp, то используйте вызов:
  1. ServletFileUpload upload = new ServletFileUpload(factory);
Ну а если ваше приложение организовано как портлет, то нужно создать другой объект:
  1. PortletFileUpload upload = new PortletFileUpload (factory);
Все эти два класса являются наследниками от org.apache.commons.fileupload.FileUploadBase. После создания парсер можно настроить указав ряд параметров:

setSizeMax – задает максимальный размер суммарно всех файлов загружаемых на сервер. Для установки предельного значения для файла в отдельности используется вызов setFileSizeMax. Также здесь можно установить с помощью вызова setProgressListener специальный класс-слушатель прогресса загрузки файлов – это может быть полезным для ajax-приложений.

Но перед тем как мы выполним парсинг запроса, необходимо проверить является ли входящий запрос “понятным” для commons fileupload. Для этого вызовите статический метод isMultipartContent, передав ему ссылку на объект входящего запроса ServletRequest.

Если метод вернет false, значит данные пришли будучи закодированными с помощью “x-www-form-urlencoded” и на этом участие commons fileupload заканчивается. Собственно, парсинг выполняется вызовом метода parseRequest. В качестве результат будет возвращен список “элементов” запроса, каждый из этих элементов представляет собой объект, реализующий интерфейс FileItem. В составе этого интерфейса есть методы позволяющие получить подробные сведения об загруженных файлах или обычных полях. Например:
  1. boolean isMultipart = ServletFileUpload.isMultipartContent(request);
  2. if (isMultipart) {
  3.   ServletFileUpload upload = new ServletFileUpload(factory);
  4.   List items = upload.parseRequest(request);
  5.   Iterator iter = items.iterator();
  6.   // получили список всех полей из html-формы
  7.   while (iter.hasNext()) {
  8.     FileItem item = (FileItem) iter.next();
  9.     // берем элемент формы и анализируем то, какого он типа
  10.     if (item.isFormField()) {
  11.        // если обычное поле, то мы можем получить его значение в выбранной кодировке 
  12.        // с помощью вызова getString(нужная кодировка)
  13.        item.getString(usedCharset);
  14.     }
  15.     else {
  16.        // если файл
  17.        // так мы получим значение оригинального имени файла
  18.        String name = item.getName();
  19.        long filelen = item.getSize();
  20.        // так мы определим его размер
  21.        // а так мы определим его content-type
  22.        String cct = item.getContentType()
  23.  
  24.        // а теперь нужно что-то сделать с самим содержимым файла ....
  25.  
  26.    }//else если это обычное текстовое поле, а не файл
  27. } // завершили обработку запроса с помощью mutipart/form-data.
Работа, собственно, с содержимым файла не сильно зависит от того, находится ли он на диске, во временном файле, или в памяти. Фактически мы можем проверить наличие файла в памяти с помощью вызова isInMemory, но практического смысла это не имеет. Вы можете получить сырое содержимое файла в виде массива байтов с помощью вызова:
  1. byte[] data = item.get();
Если файл текстовый и вы знаете его кодировку, то можно сразу прочитать все его содержимое в выбранной кодировке с помощью вызова:
  1. String fcontent = item.getString(“utf-8)
Надо сказать, что такой же метод getString используется и для получения значения обычного текстового поля.

Или вы можете получить ссылку на байтовый поток с помощью вызова:
  1. InputStream is = item.getInputStream();

Мой велосипед



В целом ничего сложного. Тем не менее, я в свое время создал сервлет-фильтр работающий совместно с fileuploads и решающим одну неприятную проблему: после того как данные были обработаны с помощью fileuploads, вы вынуждены работать с данными только показанным выше способом (через объекты FileItem). Вызов же метода request.getParameter (‘имя_переменной’) ничего не возвращает.

Как работает мой сервлет фильтр? Прежде всего, вы должны скопировать в папку lib вашего веб-приложения библиотеки нужные для работы собственно commons fileuploads, это архивы: commons-fileupload-1.1.1.jar и commons-io-1.2.jar. Затем вы регистрируете в файле web.xml сервлет фильтр и указываете то, какие адреса он должен обрабатывать. В качестве параметров сервлета нужно указать значение кодировки, в которой будут рассматриваться входящие обычные текстовые переменные (этот параметр необходим т.к. стандартная методика с request.setCharacterEncoding не работает: ведь вызов сервлета фильтра будет выполнен до того как управление попадет к вашему сервлету или jsp-файлу). Все обычные переменные будут помещены внутрь объекта request и вы можете обращаться к ним как раньше без какого либо изменения в коде. Фактически ваше приложение вообще не будет догадываться о том, что перед ним работает commons fileuploads и мой фильтр. Относительно загружаемых файлов, то все они помещаются стандартного каталога для временных файлов, а после окончания работы вашего кода будут автоматически удалены. Так что вам необходимо выполнить перемещение этих файлов в какой-то другой каталог (если, конечно, эти файлы вам нужны, в противном случае ничего с ними не делайте и они будут автоматически удалены). Фактически метод работы фильтра очень похож на принятую в php методику загрузки файлов: файлы хранятся во временном каталоге, а в специальной переменной (в атрибуты запроса помещается переменная с именем f$) хранится список описаний загруженных файлов. А вот на формате хранения данных о файлах стоит остановиться попозже подробнее.

Вот пример подключения сервлета-фильтра к вашему веб-приложению:
  1. <filter>
  2.         <filter-name>SmartFileUploads</filter-name>
  3.         <filter-class>helpers.SmartFileUploads</filter-class>
  4.         <init-param>
  5.             <param-name>force-set-encoding-urlencoded</param-name>
  6.             <param-value>utf-8</param-value>
  7.         </init-param>
  8.         <init-param>
  9.             <param-name>force-set-encoding-multipart</param-name>
  10.             <param-value>utf-8</param-value>
  11.         </init-param>
  12. </filter>
  13. <filter-mapping>
  14.         <filter-name>SmartFileUploads</filter-name>
  15.         <url-pattern>/*</url-pattern>
  16. </filter-mapping>
Обратите внимание на параметры фильтра: force-set-encoding-multipart и force-set-encoding-urlencoded. Они оба указывают на используемую при анализе полученного содержимого формы кодировку. Однако в случае если эти параметры не указаны, то их значения интерпретируются по-разному. В первом случае используется кодировка ISO8859-1, во втором же случае, запрос пропускается без парсинга входящего набора данных. И установка нужного значения кодировки должна быть выполнена пользовательским сервлетом или jsp-файлом.

Итак, доступ к обычным полям формы выполняется так, как вы привыкли (с помощью getParameter), а как быть с выходными данными.

Есть два стиля возвращаемых данных и зависящих от параметров в файле web.xml. Ни в одном из этих случаев, нет доступа к структурам данных, используемым fileuploads: фактически, разработчик может не догадывался об их существовании (плохо это или хорошо – думайте сами). Чем обусловлено существование двух стилей возвращаемых данны? Очевидно удобством работы.

Вот пример формы, которая отправит данные на сервер:
  1. <form action="servertest.jsp" method="post" enctype="multipart/form-data">
  2.     File <input type="file" name="foto" size="40" value=""/>
  3.     File 1<input type="file" name="pics[]" size="40"/>
  4.     File 2<input type="file" name="pics[]" size="40"/>
  5.     <input type="submit" name="btnsubmit" value="Send"/>
  6. </form>
Обратите внимание, что есть два элемента формы, имеющие одинаковые имена, заканчивающиеся на “[]”. Фактически вы можете загрузить на сервер набор файлов с одинаковым именем (это удобно в случае динамической генерации списка полей для загрузки файла).

Элементарная единица информации, которая хранит сведения о файле (оригинальное имя файла, его размер, mime-тип, а также путь к каталогу с временными файлами, где находится загруженный файл) - это карта HashMap . В качестве ключей этой карты выступают предопределенные имена ключей: orig_name, tmp_name, size , content-type . Все сведения об полученных файлах помещаются внутрь еще одной карты вида HashMap . В качестве, ключа этой карты выступают имена переменных формы (foto, pics[]).

В случае если имя поля формы не заканчивается на “[]”, то в качестве значения “карты” находится одиночный HashMap. В противном случае в качетсве значения “карты” выступает список (ArrayList) хранящий сведения об множестве файлов загруженных на сервер под одним и тем же именем. Рассмотрим это на примере:

Примеры работы



В конфигурационный файл приложения необходимо добавить опцию:
  1. <init-param>
  2.    <param-name>force-unified-result-style</param-name>
  3.    <param-value>false</param-value>
  4. </init-param>
Для того что бы прочитать данные в таком стиле вы следует использовать следующий код:
  1. HashMap f = (HashMap) request.getAttribute("f$");
  2. if (f != null){
  3.   out.println ("Получены в качестве параметров несколько файлов ... анализирую...");
  4.   final Iterator set = f.keySet().iterator();
  5.   while (set.hasNext()){
  6.     String varName = (String) set.next();
  7.     Object value = f.get(varName);
  8.     if (value instanceof ArrayList){
  9.        out.println("Переменная содержит список файлов");
  10.        ArrayList alistValues = (ArrayList) value;
  11.        for (Object alistValue : alistValues) {
  12.           HashMap paramFile = (HashMap) alistValue;
  13.           out.println ("-------------");
  14.           out.println ("orig_name: "+ paramFile.get("orig_name"));
  15.           out.println ("tmp_name: "+ paramFile.get("tmp_name"));
  16.           out.println ("size: "+ paramFile.get("size"));
  17.           out.println ("content-type: "+ paramFile.get("content-type"));
  18.           out.println ("-------------");
  19.         }
  20.      }
  21.      else{
  22.         out.println("Переменная содержит только один файл");
  23.         HashMap paramFile = (HashMap) value;
  24.         out.println ("-------------");
  25.         out.println ("orig_name: "+ paramFile.get("orig_name"));
  26.         out.println ("tmp_name: "+ paramFile.get("tmp_name"));
  27.         out.println ("size: "+ paramFile.get("size"));
  28.         out.println ("content-type: "+ paramFile.get("content-type"));
  29.         out.println ("-------------");
  30.      }
  31.   }
  32. }
  33. else{
  34.    out.println ("Файлов нет  ... не анализирую...");
  35. }
Возможно вам покажется громоздким выполнение проверки что же именно хранится в карте “f$”: обычный HashMap (тогда это обычный одиночный файл), или же там хранится ArrayList (когда выполняется загрузка массива файлов). С другой стороны, если вы работаете только с одиночными загружаемыми файлами, то ваш код будет наиболее простым.

А вот как будут выглядеть полученные сведения о файлах в случае использования стиля оформления результата # 1:
foto=[{orig_name=Baza_6_2.png, content-type=image/png, size=13643, 
 tmp_name=E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9822zedzed}
]
 
pics=[{orig_name=Baza_6_1.png, content-type=image/png, size=6012, 
 tmp_name=E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9823zedzed}, 
     {orig_name=Baza_6_3.png, content-type=image/png, size=31879, 
 tmp_name=E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9824zedzed}
]
 
Файлы
Получены в качестве параметров несколько файлов ... анализирую...
Переменная содержит только один файл
-------------
orig_name: Baza_6_2.png
tmp_name: E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9829zedzed
size: 13643
content-type: image/png
-------------
Переменная содержит список файлов
-------------
orig_name: Baza_6_1.png
tmp_name: E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9830zedzed
size: 6012
content-type: image/png
-------------
-------------
orig_name: Baza_6_3.png
tmp_name: E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9831zedzed
size: 31879
content-type: image/png
-------------
Во втором случае результаты разбора запроса будут унифицированы и не зависимо от того, передан один файл или их массив, в качестве значения карты “f$” будет храниться список (возможно состоящий из одного элемента). Зато такие данные удобно обрабатывать в циклах:

Вот так будет выглядеть конфигурационный параметр в файле web.xml:
  1. <init-param>
  2.   <param-name>force-unified-result-style</param-name>
  3.   <param-value>true</param-value>
  4. </init-param>
Код который формирует подобный список информации более выглядит более компактно:
  1. HashMap<String, ArrayList<HashMap<String, String>>> f = 
  2.         (HashMap<String, ArrayList<HashMap<String, String>>>)    request.getAttribute("f$");
  3. if (f != null) {
  4.    out.println("Получены в качестве параметров несколько файлов ... анализирую...");
  5.    final Iterator set = f.keySet().iterator();
  6.    while (set.hasNext()) {
  7.       String varName = (String) set.next();
  8.       Object value = f.get(varName);
  9.       out.println("Переменная содержит список файлов");
  10.       ArrayList alistValues = (ArrayList) value;
  11.       for (Object alistValue : alistValues) {
  12.          HashMap paramFile = (HashMap) alistValue;
  13.          out.println("-------------");
  14.          out.println("orig_name: " + paramFile.get("orig_name"));
  15.          out.println("tmp_name: " + paramFile.get("tmp_name"));
  16.          out.println("size: " + paramFile.get("size"));
  17.          out.println("content-type: " + paramFile.get("content-type"));
  18.          out.println("-------------");
  19.       }
  20.    }// while
  21. } else {
  22.     out.println("Файлов нет  ... не анализирую...");
  23.  }
А вот как будут выглядеть полученные сведения о файлах в случае использования стиля оформления результата # 2:
foto=[{orig_name=Baza_6_2.png, content-type=image/png, size=13643, 
       tmp_name=E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9850zedzed}
]
 
pics=[{orig_name=Baza_6_1.png, content-type=image/png, size=6012, 
    tmp_name=E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9851zedzed}, 
     {orig_name=Baza_6_3.png, content-type=image/png, size=31879, 
    tmp_name=E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9852zedzed}
]
 
Получены в качестве параметров несколько файлов ... анализирую...
Переменная содержит список файлов
-------------
orig_name: Baza_6_2.png
tmp_name: E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9850zedzed
size: 13643
content-type: image/png
-------------
Переменная содержит список файлов
-------------
orig_name: Baza_6_1.png
tmp_name: E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9851zedzed
size: 6012
content-type: image/png
-------------
-------------
orig_name: Baza_6_3.png
tmp_name: E:\Program_Files_2\apache-tomcat-6.0.14\temp\zedzed9852zedzed
size: 31879
content-type: image/png
-------------

И код самого сервлета-фильтра


  1. package helpers;
  2.  
  3. import org.apache.commons.fileupload.FileItem;
  4. import org.apache.commons.fileupload.FileItemFactory;
  5. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  6. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  7. import org.apache.commons.io.FilenameUtils;
  8.  
  9. import javax.servlet.*;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletRequestWrapper;
  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.util.*;
  15.  
  16.  
  17. public class SmartFileUploads implements Filter {
  18.     private class UTf8MakerReqiestWrapper extends HttpServletRequestWrapper {
  19.         HashMap<String, String[]> params = new HashMap<String, String[]>();
  20.  
  21.         public String getParameter(String s) {
  22.             if (params.get(s) == null) return null;
  23.             return params.get(s).length != 0 ? params.get(s)[0] : null;
  24.         }
  25.  
  26.         public Map getParameterMap() {
  27.             return params;
  28.         }
  29.  
  30.         public Enumeration getParameterNames() {
  31.             return Collections.enumeration(params.keySet());
  32.         }
  33.  
  34.         public String[] getParameterValues(String s) {
  35.             return params.get(s);
  36.         }
  37.  
  38.         private HashMap parseFile(FileItem item) throws Exception {
  39.             String name = item.getName();
  40.             long filelen = item.getSize();
  41.             HashMap<String, String> nextfile = new HashMap<String, String>();
  42.             if (name != null && (name.length() > 0)) {// если файл был загружен
  43.  
  44.                 name = FilenameUtils.getName(name);
  45.                 final File tempFile = File.createTempFile("zedzed", "zedzed");
  46.                 // здесь файл не клонируется а в случае возмоности переименовывается
  47.                 item.write(tempFile);
  48.                 nextfile.put(PARAM_ORIG_NAME, name);
  49.                 nextfile.put(PARAM_SIZE_NAME, "" + filelen);
  50.                 nextfile.put(PARAM_TMP_NAME, tempFile.getAbsolutePath());
  51.                 nextfile.put(PARAM_CONTENTTYPE_NAME, item.getContentType());
  52.                 // после парсинга файл необходимо зарегистрировать в специальном 
  53.                 // списке файлов подлежащих удалению после завершения работы фильтра
  54.                 origNames.add(tempFile.getAbsolutePath());
  55.  
  56.             } else {
  57.                 nextfile.put(PARAM_ORIG_NAME, null);
  58.                 nextfile.put(PARAM_SIZE_NAME, "-1");
  59.                 nextfile.put(PARAM_TMP_NAME, null);
  60.                 nextfile.put(PARAM_CONTENTTYPE_NAME, null);
  61.             }
  62.             return nextfile;
  63.         }
  64.  
  65.         private ArrayList<String> origNames = new ArrayList<String>();
  66.  
  67.         private UTf8MakerReqiestWrapper(HttpServletRequest hreq) throws Exception {
  68.             super(hreq);
  69.             final String usedCharset = hreq.getCharacterEncoding();
  70.  
  71.             final HashMap<String, Object> f = new HashMap<String, Object>();
  72.             hreq.setAttribute("f$", f);
  73.  
  74.             ServletFileUpload upload = new ServletFileUpload(factory);
  75.             List items = upload.parseRequest(hreq);
  76.             Iterator iter = items.iterator();
  77.  
  78.             while (iter.hasNext()) {
  79.                 FileItem item = (FileItem) iter.next();
  80.                 String fld_name = item.getFieldName();
  81.                 Boolean is_multifield = false;
  82.                 if (fld_name.endsWith("[]")) {
  83.                     fld_name = fld_name.substring(0, fld_name.length() - 2);
  84.                     is_multifield = true;
  85.                 }
  86.                 if (item.isFormField()) {
  87.                     final String fValue = item.getString(usedCharset);
  88.                     // если поле содержит массив строк
  89.                     if (is_multifield) {
  90.                         if (params.containsKey(fld_name)) {
  91.                             String[] strings = params.get(fld_name);
  92.                             String[] strings2 = new String[1 + strings.length];
  93.                             System.arraycopy(strings, 0, strings2, 0, strings.length);
  94.                             strings2[strings.length] = fValue;
  95.                             strings = null;
  96.                             params.put(fld_name, strings2);
  97.                         } else {
  98.                             params.put(fld_name, new String[]{fValue});
  99.                         }
  100.                     }//if multi values
  101.                     else {
  102.                         //if single value (skip old values)
  103.                         params.put(fld_name, new String[]{fValue});
  104.                     }
  105.                     // теперь обработаем файлы
  106.                 } else {
  107.                     // если у выполняется загрузка массива файлов
  108.                     if (is_multifield) {
  109.                         if (f.containsKey(fld_name)) {
  110.                             ((ArrayList<HashMap<String, String>>)f.get(fld_name)).add(parseFile(item));
  111.                         }//if has item by this name
  112.                         else {
  113.                             final ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
  114.                             list.add(parseFile(item));
  115.                             f.put(fld_name, list);
  116.                         }
  117.                     } else {
  118.                         // если файл загружается в единственном экземпляре, то необходимо 
  119.                         // проигнорировать все остальные значения
  120.                         // однако следует учитывать стиль формирования результата
  121.                         if (forceUnifiedResultSetStyle){
  122.                             final ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
  123.                             list.add(parseFile(item));
  124.                             f.put(fld_name, list);
  125.                         }
  126.                         else
  127.                             f.put(fld_name, parseFile(item));
  128.                     }
  129.                 }//else if fileupload, not regular fieldname
  130.                 // но это еще не все возможно что поле попало в раздел файлов и теперь необходимо сделать так
  131.                 // чтобы удалились все эти вредные файлы
  132.                 try {
  133.                     item.delete();
  134.                 } catch (Exception e) {
  135.                     log("--UTF8Maker::cannot delete tmp file (parse stage)--: " + hreq.getRequestURI());
  136.                 }
  137.  
  138.             }// while
  139.  
  140.         }// constructor
  141.  
  142.         private void cleanup() {
  143.             // необходимо удалить все файлы которые были созданы на стадии парсинга запроса,
  144.             // но все еще не были удалены после его окончания
  145.             // файл должен быть либо удален либо перенесен в другое место
  146.             for (String origName : origNames) {
  147.                 try {
  148.                     File ftmp = new File(origName);
  149.                     if (ftmp.exists())
  150.                         ftmp.delete();
  151.                 } catch (Exception e) {
  152.                     log("--UTF8Maker::cannot delete tmp file (cleanup stage)--: " + origName);
  153.                 }
  154.             }
  155.         }
  156.     }// end of CLASS
  157.  
  158.     // сведения об загруженных файлах хранятся в виде HashMap <string, String> 
  159.     // в качестве ключей этой "карты" возможны следующие значения:
  160.     // 1. Оригинальное имя файла (естественно, без каталогов)
  161.     private static final String PARAM_ORIG_NAME = "orig_name";
  162.     // 2. Имя временного файла в котором хранится информация
  163.     private static final String PARAM_TMP_NAME = "tmp_name";
  164.     // 3. Размер загруженного файла (необходимо выполнить парсинг строки)
  165.     private static final String PARAM_SIZE_NAME = "size";
  166.     // 4. Тип загруженного документа
  167.     private static final String PARAM_CONTENTTYPE_NAME = "content-type";
  168.  
  169.     // имя параметра под которым в кофигурационном файле хранятся сведения об том,
  170.     // в какую кодировку необходимо установить параметры входящего запроса
  171.     private static final String FORCE_SET_ENCODING_URLENCODED_INIT_PARAM_NAME = "force-set-encoding-urlencoded";
  172.     private static final String FORCE_SET_ENCODING_MULTIPART_INIT_PARAM_NAME = "force-set-encoding-multipart";
  173.     private static final String FORCE_UNIFIED_RESULT_STYLE_INIT_PARAM_NAME = "force-unified-result-style";
  174.  
  175.  
  176.  
  177.     private FileItemFactory factory = null;
  178.  
  179.     private String forceSetEncodingMultpart = null;
  180.     private String forceSetEncodingURLEncoded = null;
  181.     private Boolean forceUnifiedResultSetStyle = false;
  182.     private FilterConfig config = null;
  183.     private File tempDir;
  184.     private Integer MAX_SIZE_IN_MEMORY = 2000;
  185.  
  186.     // завершение жизненного цикла сервлета - не выполняется никаких действий
  187.     public void destroy() {
  188.         config.getServletContext().log("--UTF8Maker::destroy--");
  189.     }
  190.  
  191.     // выполнение фильтрации запроса
  192.     public void doFilter(ServletRequest req, ServletResponse resp,
  193.                          FilterChain chain) throws ServletException, IOException {
  194.         // если входящий запрос не Http, то мы не умеем его обрабатывать и пропускаем без изменений
  195.         if (!(req instanceof HttpServletRequest)) {
  196.             chain.doFilter(req, resp);
  197.             return;
  198.         }
  199.         HttpServletRequest hreq = (HttpServletRequest) req;
  200.         // теперь следует узнать то, следует ли нам выполнять обработку такого запроса
  201.         boolean isMultipart = ServletFileUpload.isMultipartContent(hreq);
  202.         if (isMultipart) {
  203.             // устанавливаем значение кодировки
  204.             if (forceSetEncodingMultpart != null)
  205.                 req.setCharacterEncoding(forceSetEncodingMultpart);
  206.             else
  207.                 req.setCharacterEncoding("ISO8859-1");
  208.             log("--UTF8Maker::process--: " + hreq.getRequestURI());
  209.             // нужно создать обертку над объектом входящего HttpServletRequest, 
  210.             // в котором переопределить выполняемый парсинг входных данных
  211.             UTf8MakerReqiestWrapper req2 = null;
  212.             try {
  213.                 req2 = new UTf8MakerReqiestWrapper((HttpServletRequest) req);
  214.                 // выполняем обработку запроса как будто ничего не случилось
  215.                 chain.doFilter(req2, resp);
  216.                 // теперь нужно подчистить за собой - удалить временные файлы
  217.                 req2.cleanup();
  218.             } catch (Exception e) {
  219.                 log("--UTF8Maker::exception--: " + e.getMessage());
  220.                 throw new ServletException(e);
  221.             }
  222.         } else {
  223.             // если обработка не нужна то пропускаем запрос без изменений
  224.             if (forceSetEncodingURLEncoded != null)
  225.                 req.setCharacterEncoding(forceSetEncodingURLEncoded);
  226.             log("--UTF8Maker::skip--: " + hreq.getRequestURI());
  227.             chain.doFilter(req, resp);
  228.         }
  229.  
  230.     }//end of  --doFilter--
  231.  
  232.  
  233.     // инициализация, здесь выполняется создание фабрики парсинга входящих запросов DiskFileItemFactory.
  234.     // затем выполняется чтение конфигурационного файла web.xml и установка значений 
  235.     // для переменных управляющих работой фильтра преобразований
  236.     public void init(FilterConfig config) throws ServletException {
  237.         this.config = config;
  238.         try {
  239.             final File tempFile = File.createTempFile("zedzed", "zedzed");
  240.             tempDir = tempFile.getParentFile();
  241.             tempFile.delete();
  242.             factory = new DiskFileItemFactory(MAX_SIZE_IN_MEMORY, tempDir);
  243.         } catch (IOException e) {
  244.             log("--UTF8Maker::fail init--" + e.getMessage());
  245.             throw new ServletException(e);
  246.         }
  247.         log("--UTF8Maker::init--");
  248.         forceSetEncodingMultpart = config.getInitParameter(FORCE_SET_ENCODING_MULTIPART_INIT_PARAM_NAME);
  249.         forceSetEncodingURLEncoded = config.getInitParameter(FORCE_SET_ENCODING_URLENCODED_INIT_PARAM_NAME);
  250.         forceUnifiedResultSetStyle = Boolean.parseBoolean(config.getInitParameter(FORCE_UNIFIED_RESULT_STYLE_INIT_PARAM_NAME));
  251.         log("param (forceSetEncoding) = " + forceSetEncodingMultpart + " / " + 
  252.                  forceSetEncodingURLEncoded + " / "+forceUnifiedResultSetStyle);
  253.     }
  254.  
  255.     private void log(String s) {
  256.         config.getServletContext().log(s);
  257.     }
  258.  
  259. }

Categories: Java