Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Технология передачи файлов из браузера в сервлет

div.main {margin-left: 20pt; margin-right: 20pt}Технология передачи файлов из браузера в сервлет

Данная статья посвящена проблеме передачи файлов из браузера в сервлет. Эта задача является специфической, и, к сожалению, недостаточно описана в существующей литературе. Дело в том, что технология передачи файлов на сервер через браузер отличается от общеизвестной технологии подачи запроса name-value (имя - значение). Поэтому я считаю своим долгом осветить данную проблему в своей статье.

Итак, для начала, напишем небольшой HTML-код

Листинг 1. файл file_test.html <html> <head> <title>Untitled</title> </head> <body> <form name="forma" method="post" enctype="multipart/form-data" action="http://localhost/servlet/file_servlet"> <input name="file" type="File"> <input name="button" type="Submit" value="Послать"> </form> </body> </html>

Как видно из этого кода для передачи файла на сервер недостаточно просто написать тег <input type="File" ...>, необходимо еще и правильно написать тег <form ...> (поля metod и enctype). Также очень важно, чтобы тег <input type="File" name=...> содержал поле name. Без этого условия никакой передачи данных на сервер не произойдет. Теперь напишем простой сервлет, к которому мы будем адресовать все запросы из браузера

Листинг 2. файл file_servlet.java import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.lang.*; public class test extends HttpServlet { public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/plain"); PrintWriter out = response.getWriter(); out.println("Content-Type: " + request.getHeader("Content-Type")); ServletInputStream in = request.getInputStream(); BufferedInputStream bf = new BufferedInputStream((InputStream)in); StringBuffer data = new StringBuffer(); int bit; while((bit = bf.read()) != -1) { data.append((char)bit); } String all_data = new String(data); out.println(all_data); out.close(); } }

Понятно, что здесь мы просто выводим на экран браузера содержимое входного потока ServletInputStream.

Создадим файл test.txt для для нашей задачи, и попытаемся закачать его на сервер. Мы намерено создаем именно текстовой файл, т.к. работать с текстовыми файлами гораздо удобнее и нагляднее, чем с каким-либо другими

Листинг 3. файл text.txt ------- name: Alex; age: 21; male; ------- end of file;

После того как сервлетом file_servlet будет обработан запрос, в окне нашего браузера мы увидим следующий текст Content-Type: multipart/form-data; boundary=---------------------------7d0366d5a4 -----------------------------7d018665a4 Content-Disposition: form-data; name="file"; filename="C:test.txt" Content-Type: text/plain ------- name: Alex; age: 21; male; ------- end of file; -----------------------------7d018665a4 Content-Disposition: form-data; name="button" Послать -----------------------------7d018665a4--

Давайте разберемся в его структуре. Понятно, что первая строка показывает один из заголовков запроса Content-Type, а все остальное является содержимым запроса. Первое на что обратим внимание - это на строку ---------------------------7d0366d5a4 - это так называемая граница (boundary), которая разделяет содержимое запроса на части. Эта граница извлекается из заголовка Content-Type. Content-Type: multipart/form-data; boundary=---------------------------7d0366d5a4

На самом деле, реальная граница получается добавлением двух символов '-' справа к boundary, извлеченной из заголовка Content-Type. Это сделано специально для того, чтобы в процессе анализа поступившего запроса, boundary из заголовка Content-Type не интерпретировалась как настоящая граница.

Итак, в нашем случае, запрос имеет две части. Вспомним, что в HTML-коде было именно два тэга input, заключенных в форму <form...>. Первая часть соответствует тэгу <input name="file" type="File">

а вторая

<input name="button" type="Submit" value="Послать">

Любая часть запроса (заключенная в границы - boundary), в свою очередь, состоит из двух частей - это заголовок и непосредственно полезная информация. Как же отделить эти две части друг от друга? А сделать это достаточно просто. Если вывести все содержимое запроса не в текстовой форме, как мы сейчас это сделали, а как последовательность байтов, то можно заметить, что заголовок и полезная информация разделены следующей последовательностью байтов. 13,10,13,10

т.е. это непечатаемые символы ASCII таблицы. Их также можно представить как последовательность символов 'r', 'n', 'r', 'n'. Теперь нам нужно узнать, содержится ли файл в такой или иной части запроса или нет. Нетрудно заметить, что в той части где находтся файл, заголовок состоит из двух строк: Content-Disposition и Content-Type, а также в заголовке Content-Disposition имеется подстрока filename="...". Здесь пишется имя переданного на сервер файла. Если в поле filename ничего не окажется - это будет означать, что файл на сервер вообще не пересылался.

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

Листинг 4. файл file_servlet.java import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.lang.*; import java.util.*; // этот класс хранит информацию о прочитанном файле class FileInfo { // имя файла public String filename; // указывает индекс начала //файла в массиве byte[] data public int start_index; // указывает индекс конца // файла в массиве byte[] data public int last_index; public FileInfo (String filename, int start_index, int last_index) { this.filename = filename; this.start_index = start_index; this.last_index = last_index; } // запись файла на диск public void SaveFile (byte[] data, String directory) { // если файл, то записываем на диск. if (!filename.equals("NO_FILE")) { File f = new File(directory + filename); try { FileOutputStream fos = new FileOutputStream(f); int length = last_index - start_index; fos.write(data, start_index, length); fos.close(); } catch(Exception ex) { System.out.println(ex.toString()); } } } } public class test extends HttpServlet { public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // здесь хранится содержимое запроса byte[] data; // максимальный объем хранимой информации final int size = 1024*1024; response.setContentType("text/plain"); PrintWriter out = response.getWriter(); ServletInputStream in = request.getInputStream(); BufferedInputStream bf = new BufferedInputStream((InputStream)in, size); // переменная all_data - служит для временного // хранения содержимого запроса byte[] all_data = new byte[size]; int b,j = 0; while ((b = bf.read()) != -1) { all_data[j] = (byte)b; // в j хранится количество прочитанных байт j++; } data = new byte[j]; for(int i=0; i<j; i++) data[i] = all_data[i]; all_data = null; String boundary = extractBoundary( request.getHeader("Content-Type")); FileInfo[] file = extractFiles(data, boundary); for (int i=0; i < file.length; i++) file[i].SaveFile(data, System.getProperty("user.dir") + System.getProperty("file.separator")); } // получаем имя файла, если файла нет, то пишем NO_FILE private String getFilename(String header) { String filename = ""; header.toLowerCase(); int index; if ((index = header.indexOf("filename=")) != -1) { int up_index = header.indexOf((int)'"',index + 1 + 9); // ищем закрывающую кавычку после filename=".... // 9 +1 это длина filename=" filename = header.substring(index+9+1,up_index); // в разных ОС по разному представляется, index = filename.lastIndexOf((int)'/'); // символ "file.separator" up_index = filename.lastIndexOf((int)'\'); filename = filename.substring (Math.max(index, up_index) + 1); } else filename = "NO_FILE"; return filename; } // в этой процедуре происходит // корректировка индексов(отделяется заголовок) и // находится имя файла (если есть) private void extractData(FileInfo fis, String data) { char[] ch = {'r','n','r','n'}; // этими символами отделен заголовок от содержимого // в байтах это выглядит {13,10,13,10} String new_line = new String(ch); String header; //отделяем заголовок int index = data.indexOf(new_line,2); if (index != -1) { header = data.substring(0,index); fis.filename = getFilename(header); // 4 символа - это длина new_line fis.start_index += index + 4; } } // делим исходный запрос(data) на части // и получаем информацию о каждой части. private FileInfo[] extractFiles(byte[] data, String boundary) { int i = 0, index = 0, prev_index = 0; //со строкой удобнее работать String data_str = new String(data); //для временного хранения частей сообщения Vector data_vec = new Vector(); // считаем количество частей // в сообщении(ограниченных boundary) while((index = data_str.indexOf(boundary,index)) != -1) { // первый шаг - подготовка к индексации if (i != 0) { // здесь мы выделяем сообщение, // ограниченное boundary // (все сообщение: заголовок + содержание) FileInfo f_info = new FileInfo("NO_FILE", prev_index, index - 2); //так как содержимое - //отделено от boundary двумя символами{'r','n'} //или в байтах {10,13} extractData(f_info, data_str.substring(prev_index,index)); data_vec.addElement(f_info); } index += boundary.length(); prev_index = index; i++; } // i-1 должно равняться data_vec.capacity(); FileInfo[] Data = new FileInfo[i-1]; Enumeration enum = data_vec.elements(); i=0; while(enum.hasMoreElements()) { Data[i] = (FileInfo) enum.nextElement(); i++; } return Data; } // извлекаем границу private String extractBoundary(String str) { int index = str.lastIndexOf("boundary="); // 9- число букв в "boundary=" String boundary = str.substring(index + 9); boundary = "--" + boundary; // так как реальная граница на два символа '-' длиннее, // чем записано в заголовке Content-Type. return boundary; } }

Детальное понимание этого кода я возлагаю на плечи читателей, однако, приведу некоторые пояснения

Во-первых, все содержание запроса, со всеми границами и заголовками записывается в массив байтов data. Во-вторых, создается вспомогательный класс FileInfo, который содержит информацию о каждой части сообщения. Если эта часть - файл, то содержит имя файла, и его расположение в массиве байтов data. В-третьих, там где это удобно, часть массива data переводится в объект класса String, т.к. в этом классе реализованы методы поиска, да и работать с классом удобнее, чем с массивом. Однако, все сообщение не переведено в объект класса String, как это сделано в первом варианте файла file_servlet, так как в этом случае происходит конвертация каждого байта в символ (char), а при записи файла на диск - обратно из символа в байт, при которой могут возникнуть проблемы с перекодировкой. Автор: Александр Годин, E-Mail: alexgoldd@mail.ru


  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте