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 -------------
« Про русские буквы и java | OpenID. Обзор библиотеки joid » |
Заметки про java и загрузку файлов с помощью commons fileupload
Введение
Сегодня я расскажу о том, как выполнять загрузку файлов из браузера в ваш сервлет или jsp-файл. Не секрет, что встроенной поддержки загрузки файлов в j2ee нет. Иногда по форумам кочуют самодельные скрипты парсинга http-запроса (несколько лет назад я и сам баловался написанием анализатора http-запросов), но то время прошло и теперь все большее количество java-разработчиков открывают для себя Commons FileUpload.
Напомню, что для того, чтобы html-форма могла отправить файл, необходимо указать для нее метод отправки “method” равный “post” (ну не в адресной же строке передавать содержимое файла…). А также необходимо указать способ кодирования отправляемой информации равным “multipart/form-data” (есть два алгоритма кодирования информации перед отправкой по сети: “multipart/form-data” и “x-www-form-urlencoded”). Для доступа к обычным (текстовым) полям формы мы используем либо вызовы:
String variableValue = request.getParameter(variableName);
<c:out value="${para.userFio}" />
Немножко кода
Стадия настройки: в методе init сервлет-а необходимо создать фабрику DiskFileItemFactory. Ее назначение – управлять параметрами обработки запроса. Дело в том, что все входящие переменные (как обычные, так и файловые) сохраняются либо в памяти либо на жестком диске сервера в зависимости от их размера. Откровенно говоря, мне не нравится такой подход, т.к. почти всегда данные пришедшие из текстовых полей приходится помещать в базу данных и эти данные действительно должны находиться в памяти. А вот ситуации с обработкой пришедших файлов (например, изменение размера картинки или конвертация видео-файла являются достаточно редкими) обычно файл просто переносится в определенную папку на сервера. А в базу данных заносится имя этого файла. Возвращаясь к вопросу создания фабрики, вы должны указать в качестве параметров конструктора число (играющее роль порога для принятия решения: сохранять ли входную переменную на диск или в файл), вторым же параметром при создании DiskFileItemFactory выступает путь к каталогу, где будут сохраняться временные файлы. Если вы эти значения не укажите, то в качестве пороговой величины будет принята отметка в 10240 байт, а для каталога с файлами используется значение возвращаемой системной переменной:
System.getProperty("java.io.tmpdir").
<listener>
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
ServletFileUpload upload = new ServletFileUpload(factory);
PortletFileUpload upload = new PortletFileUpload (factory);
setSizeMax – задает максимальный размер суммарно всех файлов загружаемых на сервер. Для установки предельного значения для файла в отдельности используется вызов setFileSizeMax. Также здесь можно установить с помощью вызова setProgressListener специальный класс-слушатель прогресса загрузки файлов – это может быть полезным для ajax-приложений.
Но перед тем как мы выполним парсинг запроса, необходимо проверить является ли входящий запрос “понятным” для commons fileupload. Для этого вызовите статический метод isMultipartContent, передав ему ссылку на объект входящего запроса ServletRequest.
Если метод вернет false, значит данные пришли будучи закодированными с помощью “x-www-form-urlencoded” и на этом участие commons fileupload заканчивается. Собственно, парсинг выполняется вызовом метода parseRequest. В качестве результат будет возвращен список “элементов” запроса, каждый из этих элементов представляет собой объект, реализующий интерфейс FileItem. В составе этого интерфейса есть методы позволяющие получить подробные сведения об загруженных файлах или обычных полях. Например:
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request);
Iterator iter = items.iterator();
// получили список всех полей из html-формы
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
// берем элемент формы и анализируем то, какого он типа
if (item.isFormField()) {
// если обычное поле, то мы можем получить его значение в выбранной кодировке
// с помощью вызова getString(нужная кодировка)
item.getString(usedCharset);
}
else {
// если файл
// так мы получим значение оригинального имени файла
String name = item.getName();
long filelen = item.getSize();
// так мы определим его размер
// а так мы определим его content-type
String cct = item.getContentType()
// а теперь нужно что-то сделать с самим содержимым файла ....
}//else если это обычное текстовое поле, а не файл
} // завершили обработку запроса с помощью mutipart/form-data.
byte[] data = item.get();
String fcontent = item.getString(“utf-8”)
Или вы можете получить ссылку на байтовый поток с помощью вызова:
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$) хранится список описаний загруженных файлов. А вот на формате хранения данных о файлах стоит остановиться попозже подробнее.
Вот пример подключения сервлета-фильтра к вашему веб-приложению:
<filter>
<filter-name>SmartFileUploads</filter-name>
<filter-class>helpers.SmartFileUploads</filter-class>
<init-param>
<param-name>force-set-encoding-urlencoded</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>force-set-encoding-multipart</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SmartFileUploads</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Итак, доступ к обычным полям формы выполняется так, как вы привыкли (с помощью getParameter), а как быть с выходными данными.
Есть два стиля возвращаемых данных и зависящих от параметров в файле web.xml. Ни в одном из этих случаев, нет доступа к структурам данных, используемым fileuploads: фактически, разработчик может не догадывался об их существовании (плохо это или хорошо – думайте сами). Чем обусловлено существование двух стилей возвращаемых данны? Очевидно удобством работы.
Вот пример формы, которая отправит данные на сервер:
<form action="servertest.jsp" method="post" enctype="multipart/form-data">
File <input type="file" name="foto" size="40" value=""/>
File 1<input type="file" name="pics[]" size="40"/>
File 2<input type="file" name="pics[]" size="40"/>
<input type="submit" name="btnsubmit" value="Send"/>
</form>
Элементарная единица информации, которая хранит сведения о файле (оригинальное имя файла, его размер, mime-тип, а также путь к каталогу с временными файлами, где находится загруженный файл) - это карта HashMap
В случае если имя поля формы не заканчивается на “[]”, то в качестве значения “карты” находится одиночный HashMap. В противном случае в качетсве значения “карты” выступает список (ArrayList) хранящий сведения об множестве файлов загруженных на сервер под одним и тем же именем. Рассмотрим это на примере:
Примеры работы
В конфигурационный файл приложения необходимо добавить опцию:
<init-param>
<param-name>force-unified-result-style</param-name>
<param-value>false</param-value>
</init-param>
HashMap f = (HashMap) request.getAttribute("f$");
if (f != null){
out.println ("Получены в качестве параметров несколько файлов ... анализирую...");
final Iterator set = f.keySet().iterator();
while (set.hasNext()){
String varName = (String) set.next();
Object value = f.get(varName);
if (value instanceof ArrayList){
out.println("Переменная содержит список файлов");
ArrayList alistValues = (ArrayList) value;
for (Object alistValue : alistValues) {
HashMap paramFile = (HashMap) alistValue;
out.println ("-------------");
out.println ("orig_name: "+ paramFile.get("orig_name"));
out.println ("tmp_name: "+ paramFile.get("tmp_name"));
out.println ("size: "+ paramFile.get("size"));
out.println ("content-type: "+ paramFile.get("content-type"));
out.println ("-------------");
}
}
else{
out.println("Переменная содержит только один файл");
HashMap paramFile = (HashMap) value;
out.println ("-------------");
out.println ("orig_name: "+ paramFile.get("orig_name"));
out.println ("tmp_name: "+ paramFile.get("tmp_name"));
out.println ("size: "+ paramFile.get("size"));
out.println ("content-type: "+ paramFile.get("content-type"));
out.println ("-------------");
}
}
}
else{
out.println ("Файлов нет ... не анализирую...");
}
А вот как будут выглядеть полученные сведения о файлах в случае использования стиля оформления результата # 1:
Вот так будет выглядеть конфигурационный параметр в файле web.xml:
<init-param>
<param-name>force-unified-result-style</param-name>
<param-value>true</param-value>
</init-param>
HashMap<String, ArrayList<HashMap<String, String>>> f =
(HashMap<String, ArrayList<HashMap<String, String>>>) request.getAttribute("f$");
if (f != null) {
out.println("Получены в качестве параметров несколько файлов ... анализирую...");
final Iterator set = f.keySet().iterator();
while (set.hasNext()) {
String varName = (String) set.next();
Object value = f.get(varName);
out.println("Переменная содержит список файлов");
ArrayList alistValues = (ArrayList) value;
for (Object alistValue : alistValues) {
HashMap paramFile = (HashMap) alistValue;
out.println("-------------");
out.println("orig_name: " + paramFile.get("orig_name"));
out.println("tmp_name: " + paramFile.get("tmp_name"));
out.println("size: " + paramFile.get("size"));
out.println("content-type: " + paramFile.get("content-type"));
out.println("-------------");
}
}// while
} else {
out.println("Файлов нет ... не анализирую...");
}
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 -------------
И код самого сервлета-фильтра
package helpers;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SmartFileUploads implements Filter {
private class UTf8MakerReqiestWrapper extends HttpServletRequestWrapper {
HashMap<String, String[]> params = new HashMap<String, String[]>();
public String getParameter(String s) {
if (params.get(s) == null) return null;
return params.get(s).length != 0 ? params.get(s)[0] : null;
}
public Map getParameterMap() {
return params;
}
public Enumeration getParameterNames() {
return Collections.enumeration(params.keySet());
}
public String[] getParameterValues(String s) {
return params.get(s);
}
private HashMap parseFile(FileItem item) throws Exception {
String name = item.getName();
long filelen = item.getSize();
HashMap<String, String> nextfile = new HashMap<String, String>();
if (name != null && (name.length() > 0)) {// если файл был загружен
name = FilenameUtils.getName(name);
final File tempFile = File.createTempFile("zedzed", "zedzed");
// здесь файл не клонируется а в случае возмоности переименовывается
item.write(tempFile);
nextfile.put(PARAM_ORIG_NAME, name);
nextfile.put(PARAM_SIZE_NAME, "" + filelen);
nextfile.put(PARAM_TMP_NAME, tempFile.getAbsolutePath());
nextfile.put(PARAM_CONTENTTYPE_NAME, item.getContentType());
// после парсинга файл необходимо зарегистрировать в специальном
// списке файлов подлежащих удалению после завершения работы фильтра
origNames.add(tempFile.getAbsolutePath());
} else {
nextfile.put(PARAM_ORIG_NAME, null);
nextfile.put(PARAM_SIZE_NAME, "-1");
nextfile.put(PARAM_TMP_NAME, null);
nextfile.put(PARAM_CONTENTTYPE_NAME, null);
}
return nextfile;
}
private ArrayList<String> origNames = new ArrayList<String>();
private UTf8MakerReqiestWrapper(HttpServletRequest hreq) throws Exception {
super(hreq);
final String usedCharset = hreq.getCharacterEncoding();
final HashMap<String, Object> f = new HashMap<String, Object>();
hreq.setAttribute("f$", f);
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(hreq);
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
String fld_name = item.getFieldName();
Boolean is_multifield = false;
if (fld_name.endsWith("[]")) {
fld_name = fld_name.substring(0, fld_name.length() - 2);
is_multifield = true;
}
if (item.isFormField()) {
final String fValue = item.getString(usedCharset);
// если поле содержит массив строк
if (is_multifield) {
if (params.containsKey(fld_name)) {
String[] strings = params.get(fld_name);
String[] strings2 = new String[1 + strings.length];
System.arraycopy(strings, 0, strings2, 0, strings.length);
strings2[strings.length] = fValue;
strings = null;
params.put(fld_name, strings2);
} else {
params.put(fld_name, new String[]{fValue});
}
}//if multi values
else {
//if single value (skip old values)
params.put(fld_name, new String[]{fValue});
}
// теперь обработаем файлы
} else {
// если у выполняется загрузка массива файлов
if (is_multifield) {
if (f.containsKey(fld_name)) {
((ArrayList<HashMap<String, String>>)f.get(fld_name)).add(parseFile(item));
}//if has item by this name
else {
final ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
list.add(parseFile(item));
f.put(fld_name, list);
}
} else {
// если файл загружается в единственном экземпляре, то необходимо
// проигнорировать все остальные значения
// однако следует учитывать стиль формирования результата
if (forceUnifiedResultSetStyle){
final ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
list.add(parseFile(item));
f.put(fld_name, list);
}
else
f.put(fld_name, parseFile(item));
}
}//else if fileupload, not regular fieldname
// но это еще не все возможно что поле попало в раздел файлов и теперь необходимо сделать так
// чтобы удалились все эти вредные файлы
try {
item.delete();
} catch (Exception e) {
log("--UTF8Maker::cannot delete tmp file (parse stage)--: " + hreq.getRequestURI());
}
}// while
}// constructor
private void cleanup() {
// необходимо удалить все файлы которые были созданы на стадии парсинга запроса,
// но все еще не были удалены после его окончания
// файл должен быть либо удален либо перенесен в другое место
for (String origName : origNames) {
try {
File ftmp = new File(origName);
if (ftmp.exists())
ftmp.delete();
} catch (Exception e) {
log("--UTF8Maker::cannot delete tmp file (cleanup stage)--: " + origName);
}
}
}
}// end of CLASS
// сведения об загруженных файлах хранятся в виде HashMap <string, String>
// в качестве ключей этой "карты" возможны следующие значения:
// 1. Оригинальное имя файла (естественно, без каталогов)
private static final String PARAM_ORIG_NAME = "orig_name";
// 2. Имя временного файла в котором хранится информация
private static final String PARAM_TMP_NAME = "tmp_name";
// 3. Размер загруженного файла (необходимо выполнить парсинг строки)
private static final String PARAM_SIZE_NAME = "size";
// 4. Тип загруженного документа
private static final String PARAM_CONTENTTYPE_NAME = "content-type";
// имя параметра под которым в кофигурационном файле хранятся сведения об том,
// в какую кодировку необходимо установить параметры входящего запроса
private static final String FORCE_SET_ENCODING_URLENCODED_INIT_PARAM_NAME = "force-set-encoding-urlencoded";
private static final String FORCE_SET_ENCODING_MULTIPART_INIT_PARAM_NAME = "force-set-encoding-multipart";
private static final String FORCE_UNIFIED_RESULT_STYLE_INIT_PARAM_NAME = "force-unified-result-style";
private FileItemFactory factory = null;
private String forceSetEncodingMultpart = null;
private String forceSetEncodingURLEncoded = null;
private Boolean forceUnifiedResultSetStyle = false;
private FilterConfig config = null;
private File tempDir;
private Integer MAX_SIZE_IN_MEMORY = 2000;
// завершение жизненного цикла сервлета - не выполняется никаких действий
public void destroy() {
config.getServletContext().log("--UTF8Maker::destroy--");
}
// выполнение фильтрации запроса
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws ServletException, IOException {
// если входящий запрос не Http, то мы не умеем его обрабатывать и пропускаем без изменений
if (!(req instanceof HttpServletRequest)) {
chain.doFilter(req, resp);
return;
}
HttpServletRequest hreq = (HttpServletRequest) req;
// теперь следует узнать то, следует ли нам выполнять обработку такого запроса
boolean isMultipart = ServletFileUpload.isMultipartContent(hreq);
if (isMultipart) {
// устанавливаем значение кодировки
if (forceSetEncodingMultpart != null)
req.setCharacterEncoding(forceSetEncodingMultpart);
else
req.setCharacterEncoding("ISO8859-1");
log("--UTF8Maker::process--: " + hreq.getRequestURI());
// нужно создать обертку над объектом входящего HttpServletRequest,
// в котором переопределить выполняемый парсинг входных данных
UTf8MakerReqiestWrapper req2 = null;
try {
req2 = new UTf8MakerReqiestWrapper((HttpServletRequest) req);
// выполняем обработку запроса как будто ничего не случилось
chain.doFilter(req2, resp);
// теперь нужно подчистить за собой - удалить временные файлы
req2.cleanup();
} catch (Exception e) {
log("--UTF8Maker::exception--: " + e.getMessage());
throw new ServletException(e);
}
} else {
// если обработка не нужна то пропускаем запрос без изменений
if (forceSetEncodingURLEncoded != null)
req.setCharacterEncoding(forceSetEncodingURLEncoded);
log("--UTF8Maker::skip--: " + hreq.getRequestURI());
chain.doFilter(req, resp);
}
}//end of --doFilter--
// инициализация, здесь выполняется создание фабрики парсинга входящих запросов DiskFileItemFactory.
// затем выполняется чтение конфигурационного файла web.xml и установка значений
// для переменных управляющих работой фильтра преобразований
public void init(FilterConfig config) throws ServletException {
this.config = config;
try {
final File tempFile = File.createTempFile("zedzed", "zedzed");
tempDir = tempFile.getParentFile();
tempFile.delete();
factory = new DiskFileItemFactory(MAX_SIZE_IN_MEMORY, tempDir);
} catch (IOException e) {
log("--UTF8Maker::fail init--" + e.getMessage());
throw new ServletException(e);
}
log("--UTF8Maker::init--");
forceSetEncodingMultpart = config.getInitParameter(FORCE_SET_ENCODING_MULTIPART_INIT_PARAM_NAME);
forceSetEncodingURLEncoded = config.getInitParameter(FORCE_SET_ENCODING_URLENCODED_INIT_PARAM_NAME);
forceUnifiedResultSetStyle = Boolean.parseBoolean(config.getInitParameter(FORCE_UNIFIED_RESULT_STYLE_INIT_PARAM_NAME));
log("param (forceSetEncoding) = " + forceSetEncodingMultpart + " / " +
forceSetEncodingURLEncoded + " / "+forceUnifiedResultSetStyle);
}
private void log(String s) {
config.getServletContext().log(s);
}
}
« Про русские буквы и java | OpenID. Обзор библиотеки joid » |