Sendfile on Tomcat, Apache, Nginx

Рубрика: Development, Java | 4 September 2008, 14:53 | Vadim Voituk

Наверное все, кто хоть когда-либо использовал web-сервер nginx, знают о такой его возможности как “X-Accel-Redirect”.
Для тех кто не знает / не помнит – напомню. 

Суть состоит в том, что при необходимости отдать клиенту файл посредством своего скрипта, можно не писать код, который будет читать байтики из файла и писать их в сокет, а отправить специальный response header, получив который nginx сам начнет отправку файла.
Например на PHP это выглядит так:

header("X-Accel-Redirect: /protected/iso.img");

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

Собсно как подобное реализуется на nginx – написано парой строчек выше.
Если что не очень ясно – Google в помощь.

Не так давно узнал даже как реализовать описанное и в старом-добром Apache.
Для этого нужно установить дополнительный модуль mod_xsendfile, активировать, ну а дальше все аналогично nginx, только c отличным именем response header:

X-Sendfile: /path/to/file

Есть только одно НО в этом способе – я его ещё не пробовал :)
Если кто имеет опыт работы с этим модулем – дайте знать, хочется услышать отзывы и мнения :)

А недавно пришлось реализовать отдачу достаточно тяжеловесного контента на Tomcat, и захотелось чего-то подобного.

Путем недолгого гугления было найдено такое решение:

В первую очередь необходимо активировать NIO connector в настройках сервера
(документация говорит что можно и APR, но я не пробовал)

Для этого в conf/server.xml вместо стандартного

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
    redirectPort="8443" />

Пишем

<Connector port="8180" protocol="org.apache.coyote.http11.Http11NioProtocol"
redirectPort="8443" />

После этого проверяем поддержку sendfile наличием аттрибута
org.apache.tomcat.sendfile.support в request-е сервлета

Если значение установлено в false – то в Connector-е дописываем useSendfile="true"

Код отправки файла клиенту выглядит так:


if (request.getAttribute("org.apache.tomcat.sendfile.support")==Boolean.TRUE){
  File f = new File(cont.getRealPath("/3499.3gp"));

  long start = 0L;
  long end = f.length();

  request.setAttribute("org.apache.tomcat.sendfile.filename", f.getCanonicalPath());
  request.setAttribute("org.apache.tomcat.sendfile.start", start);
  request.setAttribute("org.apache.tomcat.sendfile.end", end);
  request.setAttribute("org.apache.tomcat.sendfile.token", this);

  response.setContentLength(new Long(end).intValue());
}

Немного отличается от того, как это выглядит в nginx и apache, но ничто не мешает вынести эту логику в фильтр, который будет срабатывать при наличии соответствующего HTTP заголовка в response-e

Комментариев: 13

13 Responses to “Sendfile on Tomcat, Apache, Nginx”

Комментарии:

  1. Stepan

    Lighttpd тоже поддерживает X-Sendfile.

  2. Stepan

    Я не понял, зачем такие сложности? Почему нельзя просто написать:

    response.addHeader(“X-Sendfile”, “/path/to/file”);

  3. Vadim Voituk

    Степан,
    Про lighttpd не знаю – никогда с ним не стыкался.

    Просто написать
    response.addHeader("X-Sendfile", "/path/to/file");
    прокатит только если у тебя Tomcat спрятан за каким-нить nginx, который этот X-Sendfile поймет и выполнит отдачу.
    В случае же если приходится отдавать файл Tomcat-ом – то нужно делать как описано выше :)

  4. Степан

    А, я понял, сам Tomcat поддерживает sendfile. Как интересно.

  5. Vadim Voituk

    Ага, именно так.
    Причем если нужно отдать только часть файла, то это можно регулировать аттрибутами
    org.apache.tomcat.sendfile.start
    org.apache.tomcat.sendfile.end

  6. DieZ

    Ещё не пробовал запустить, но конструкция (new Long(end).intValue() мне изначально не нравится — файлы больше 2Гб по сети не передаются, чтоль?

  7. Vadim Voituk

    DieZ,
    Хороший вопрос, но скорее всего
    уже к разработчикам ServletAPI

  8. akabanov

    Ставьте длину не ч-з setContentLength хелпер, а ч-з addHeader(“Content-Length” и будет вам лонговое счастье :)

  9. Vadim Voituk

    @akabanov
    ХитEр :)

  10. filinberg

    Кто нибудь ограничение ширины канала при скачивании делал при помощи tomcat + sendfile ?

  11. Vadim Voituk

    @filinberg – IMHO не томкатовое это дело – лучше доверить это nginx или apache, что стоят на фронтенде.

  12. filinberg

    согласен, но это для статических файлов, параллельно надо решить задачу и мониторинга трафика

  13. filinberg

    Помогло вытаскивание org.apache.tomcat.util.net.NioChannel при помощи рефлексии из HttpServletResponse, все досталось и даже работает, теперь напрямую в сервлете через sendfile посылаю файл кусками.

    SendfileData sendfileData = new NioEndpoint.SendfileData();
    sendfileData.fileName = file.getCanonicalPath();
    sendfileData.pos = 0L;
    sendfileData.length = file.length();

    // Затем как в исходниках Http11NioProcessor (Http11ArpProcessor)
    KeyAttachment ka = (KeyAttachment) socket.getAttachment(false);
    ka.setSendfileData(sendfileData);
    sendfileData.keepAlive = true;
    SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
    boolean openSocket = socket.getPoller().processSendfile(key, ka, true);

    Оформил этот кусок как опцию (т.е. если доступен sendfile то задействуем), вполне так решение …

Leave a Reply