«1. Обзор

Общей особенностью веб-приложений является возможность загрузки файлов.

В этом руководстве мы рассмотрим простой пример создания загружаемого файла и его обслуживания из приложения Java Servlet.

Используемый нами файл будет из ресурсов веб-приложения.

2. Зависимости Maven

При использовании Jakarta EE нам не нужно добавлять какие-либо зависимости. Однако, если мы используем Java SE, нам понадобится зависимость javax.servlet-api:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

Последнюю версию зависимости можно найти здесь.

3. Сервлет

Давайте сначала посмотрим на код, а затем выясним, что происходит:

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
    private final int ARBITARY_SIZE = 1048;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
    
        resp.setContentType("text/plain");
        resp.setHeader("Content-disposition", "attachment; filename=sample.txt");

        try(InputStream in = req.getServletContext().getResourceAsStream("/WEB-INF/sample.txt");
          OutputStream out = resp.getOutputStream()) {

            byte[] buffer = new byte[ARBITARY_SIZE];
        
            int numBytesRead;
            while ((numBytesRead = in.read(buffer)) > 0) {
                out.write(buffer, 0, numBytesRead);
            }
        }
    }
}

3.1. Конечная точка запроса

Аннотация @WebServlet(“/download”) помечает класс DownloadServlet для обслуживания запросов, направленных на конечную точку “/download”.

Кроме того, мы можем сделать это, описав сопоставление в файле web.xml.

3.2. Response Content-Type

Объект HttpServletResponse имеет метод setContentType, который мы можем использовать для установки заголовка Content-Type ответа HTTP.

Content-Type — это историческое имя свойства заголовка. Другим названием был тип MIME (многоцелевые расширения почты Интернета). Теперь мы просто ссылаемся на значение как тип носителя.

Это значение может быть «application/pdf», «text/plain», «text/html», «image/jpg» и т. д., официальный список поддерживается Управлением по присвоению номеров в Интернете ( IANA), и его можно найти здесь.

В нашем примере мы используем простой текстовый файл. Content-Type для текстового файла — text/plain.

3.3. Response Content-Disposition

Установка заголовка Content-Disposition в объекте ответа сообщает браузеру, как обрабатывать файл, к которому он обращается.

Браузеры понимают использование Content-Disposition как соглашение, но на самом деле это не часть стандарта HTTP. У W3 есть памятка по использованию Content-Disposition, которую можно прочитать здесь.

Значения Content-Disposition для основной части ответа будут либо «inline» (для отображаемого содержимого веб-страницы), либо «attachment» (для загружаемого файла).

Если не указано иное, Content-Disposition по умолчанию имеет значение «inline».

Используя необязательный параметр заголовка, мы можем указать имя файла «sample.txt».

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

Точное действие будет зависеть от браузера.

3.4. Чтение из файла и запись в выходной поток

В оставшихся строках кода мы берем ServletContext из запроса и используем его для получения файла в «/WEB-INF/sample.txt».

Используя HttpServletResponse#getOutputStream(), мы затем читаем из входного потока ресурса и записываем в выходной поток ответа.

Размер используемого массива байтов произволен. Мы можем определить размер на основе объема памяти, который разумно выделить для передачи данных из InputStream в OutputStream; чем меньше число, тем больше петель; чем больше число, тем выше использование памяти.

Этот цикл продолжается до тех пор, пока numByteRead не станет равным 0, поскольку это указывает на конец файла.

3.5. Close and Flush

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

Используя оператор try-with-resources, приложение автоматически закроет любой экземпляр AutoCloseable, определенный как часть оператора try. Подробнее о попытке с ресурсами читайте здесь.

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

3.6. Загрузка файла

Теперь, когда все готово, мы готовы запустить наш сервлет.

Теперь, когда мы посещаем относительную конечную точку «/download», наш браузер попытается загрузить файл как «simple.txt».

4. Заключение

«Загрузка файла из сервлета становится простым процессом. Использование потоков позволяет нам передавать данные в виде байтов, а типы мультимедиа сообщают браузеру клиента, какой тип данных ожидать.

Браузер должен определить, как обрабатывать ответ, однако мы можем дать некоторые рекомендации с заголовком Content-Disposition.

Весь код в этой статье можно найти на GitHub.