Загрузка файлов на чистом nginx

Захотелось мне написать файлообменник (для личных нужд, с нуля), да не простой, а с красивым прогресс-баром, — с отображением процесса загрузки файлов на сайт.
И остановился я на чисто серверном решении nginx с модулями nginx-upload и nginx-upload-progress.

nginx не нуждается в описании; nginx-upload управляет процессом загрузки данных, который затем может передать результат на бэкенд, — ваш PHP скрипт (или еще куда); nginx-upload-progress же отвечает исключительно за информирование о процессе загрузки, — все написано в документации, но в кратце лишь скажу, что со страницы выполняется ajax-запрос на сервер со специальным http-заголком, в котором хранится уникальный id, и в json-ответе по этому id мы можем узнать состояние загрузки данных.

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

Установка

Скачиваем, распаковываем и переименовываем (для чистоты) оба модуля.

<code class=" lua">$ wget http://www.grid.net.ru/nginx/download/nginx_upload_module-<span class="number">2.2</span>.<span class="number">0</span>.tar.gz
$ wget -O upload_progress.tar.gz https://github.com/masterzen/nginx-upload-progress-<span class="built_in">module</span>/tarball/v0.<span class="number">8.4</span>
<code class=" ruby"><span class="variable">$ </span><span class="identifier">tar</span> <span class="identifier">xf</span> <span class="identifier">nginx_upload_module</span>-<span class="number">2.2</span>.<span class="number">0</span>.<span class="identifier">tar</span>.<span class="identifier">gz</span>
<span class="variable">$ </span><span class="identifier">tar</span> <span class="identifier">xf</span> <span class="identifier">upload_progress</span>.<span class="identifier">tar</span>.<span class="identifier">gz</span>
<code class=" ruby"><span class="variable">$ </span><span class="identifier">mv</span> <span class="identifier">nginx_upload_module</span>-<span class="number">2.2</span>.<span class="number">0</span>/ <span class="identifier">nginx</span>-<span class="identifier">upload</span>
<span class="variable">$ </span><span class="identifier">mv</span> <span class="identifier">masterzen</span>-<span class="identifier">nginx</span>-<span class="identifier">upload</span>-<span class="identifier">progress</span>-<span class="class"><span class="keyword">module</span>-82<span class="title">b35fc</span>/ <span class="title">nginx</span>-<span class="title">upload</span>-<span class="title">progress</span></span>
<span class="variable">$ </span><span class="identifier">rm</span> <span class="identifier">nginx_upload_module</span>-<span class="number">2.2</span>.<span class="number">0</span>.<span class="identifier">tar</span>.<span class="identifier">gz</span> <span class="identifier">upload_progress</span>.<span class="identifier">tar</span>.<span class="identifier">gz</span>

И прежде всего, нам нужно пересобрать nginx для добавления этих двух модулей. У всех дистрибутивы с пакетными менеджерами разные, каждый делает это по-своему… В любом случае, вам нужно только добавить два параметра к ./configure и запустить сборку:

<code class=" lua">./configure 
    ...
    <span class="comment">--add-module="../nginx-upload" </span>
    <span class="comment">--add-module="../nginx-upload-progress"</span>

Настройка

Когда nginx собрался и готов к запуску, приступаем к настройке модулей. Вот рабочий пример моего конфига:

<code class=" bash">...

events {
  ...
}

http {
  ...

  <span class="comment"># подключаем nginx-upload-progress модуль, называем его "upload"</span>
  upload_progress upload <span class="number">2</span>m;

  server {

    <span class="comment"># все стандартно</span>
    listen localhost;
    server_name localhost;

    root /srv/http/localhost;

    <span class="comment"># настройка nginx-upload модуля</span>
    <span class="comment"># сюда будут отправляться данные из POST форм</span>
    location = /upload/share {

      <span class="comment"># увеличиваем лимит на размер загружаемых данных</span>
      client_max_body_size <span class="number">250</span>m;

      <span class="comment"># указываем бэкенд, который выполнится уже после загрузки данных</span>
      <span class="comment"># это может быть ваш PHP скрипт для управления файлами</span>
      <span class="comment"># и директорию, куда сохраняются загруженные файлы</span>
      upload_pass /upload;
      upload_store /tmp;

      <span class="comment"># укажем, какие дополнительные данные передать бэкенду</span>
      upload_set_form_field <span class="variable">$upload_field_name</span>.name <span class="string">"<span class="variable">$upload_file_name</span>"</span>;
      upload_set_form_field <span class="variable">$upload_field_name</span>.content_type <span class="string">"<span class="variable">$upload_content_type</span>"</span>;
      upload_set_form_field <span class="variable">$upload_field_name</span>.path <span class="string">"<span class="variable">$upload_tmp_path</span>"</span>;

      upload_aggregate_form_field <span class="string">"<span class="variable">$upload_field_name</span>.md5"</span> <span class="string">"<span class="variable">$upload_file_md5</span>"</span>;
      upload_aggregate_form_field <span class="string">"<span class="variable">$upload_field_name</span>.size"</span> <span class="string">"<span class="variable">$upload_file_size</span>"</span>;

      <span class="comment"># в случае возникновения этих ошибок файлы будут удалены</span>
      upload_cleanup <span class="number">400</span> <span class="number">404</span> <span class="number">499</span> <span class="number">500</span>-<span class="number">505</span>;

      <span class="comment"># урезаем скорость</span>
      <span class="comment"># это мне необходимо для долгой загрузки файлов</span>
      <span class="comment"># чтобы дебажить скрипт и успеть налюбоваться на процесс загрузки</span>
      upload_limit_rate <span class="number">8</span>k;

      <span class="comment"># включаем информирование для "upload" (см. в начале)</span>
      track_uploads upload <span class="number">1</span>m;
    }

    <span class="comment"># сюда приходят ajax-запросы со страницы</span>
    location = /upload/status {

      <span class="comment"># информируем их о процессе загрузки</span>
      report_uploads upload;
    }
  }
}

Запуск

И осталось только нарисовать красивую форму для загрузки файлов… Ну, как сказать, «красивую». Программист — не дизайнер, сами понимаете.

cat << EOF > /srv/http/localhost/index.html

Скрытый текст

<code class=" html"><span class="doctype"><!doctype html></span>
<span class="tag"><<span class="keyword">html</span><span class="attribute"> lang=<span class="value">"ru"</span></span>></span>
  <span class="tag"><<span class="keyword">head</span>></span>
    <span class="tag"><<span class="keyword">meta</span><span class="attribute"> charset=<span class="value">"utf-8"</span></span>></span>
    <span class="tag"><<span class="keyword">script</span>></span><span class="javascript">
      <span class="function"><span class="keyword">function</span> <span class="title">add</span><span class="params">()</span> {</span>
        <span class="keyword">if</span> (parseInt(document.getElementById(<span class="string">'count'</span>).getAttribute(<span class="string">'value'</span>)) < <span class="number">8</span>) {
          <span class="keyword">var</span> input = document.createElement(<span class="string">'input'</span>);
          input.setAttribute(<span class="string">'type'</span>,<span class="string">'file'</span>);
          input.setAttribute(<span class="string">'multiple'</span>,<span class="string">''</span>);
          input.setAttribute(<span class="string">'name'</span>,<span class="string">'file[]'</span>);
          document.getElementById(<span class="string">'multiple'</span>).appendChild(input);
          document.getElementById(<span class="string">'multiple'</span>).appendChild(document.createElement(<span class="string">'br'</span>));
          document.getElementById(<span class="string">'count'</span>).setAttribute(<span class="string">'value'</span>,parseInt(document.getElementById(<span class="string">'count'</span>).getAttribute(<span class="string">'value'</span>))+<span class="number">1</span>);
        }
        <span class="keyword">else</span> {
          alert(<span class="string">'Можно загрузить не более 8 файлов за раз.'</span>);
        }
      }
      <span class="function"><span class="keyword">function</span> <span class="title">progress</span><span class="params">()</span> {</span>
        <span class="keyword">var</span> ms = <span class="keyword">new</span> Date().getTime() / <span class="number">1000</span>;
        rq = <span class="number">0</span>;
        id = <span class="string">""</span>;
        <span class="keyword">for</span> (i = <span class="number">0</span>; i < <span class="number">32</span>; i++) {
          id += Math.floor(Math.random() * <span class="number">16</span>).toString(<span class="number">16</span>);
        }
        document.getElementById(<span class="string">'upload'</span>).action = <span class="string">"/upload/share?X-Progress-ID="</span> + id;
        document.getElementById(<span class="string">'status'</span>).style.display = <span class="string">'block'</span>
        interval = window.setInterval(<span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span> fetch(id, ms); }, <span class="number">1000</span>);
        <span class="keyword">return</span> <span class="literal">true</span>;
      }
      <span class="function"><span class="keyword">function</span> <span class="title">fetch</span><span class="params">(id, ms)</span> {</span>
        <span class="keyword">var</span> fetch = <span class="keyword">new</span> XMLHttpRequest();
        fetch.open(<span class="string">"GET"</span>, <span class="string">"/upload/status"</span>, <span class="number">1</span>);
        fetch.setRequestHeader(<span class="string">"X-Progress-ID"</span>, id);
        fetch.onreadystatechange = <span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span>
          <span class="keyword">if</span> (fetch.readyState == <span class="number">4</span>) {
            <span class="keyword">if</span> (fetch.status == <span class="number">200</span>) {
              <span class="keyword">var</span> now = <span class="keyword">new</span> Date().getTime() / <span class="number">1000</span>;
              <span class="keyword">var</span> upload = eval(fetch.responseText);
              <span class="keyword">if</span> (upload.state == <span class="string">'uploading'</span>) {
                <span class="keyword">var</span> diff = upload.size - upload.received;
                <span class="keyword">var</span> rate = upload.received / upload.size;
                <span class="keyword">var</span> elapsed = now - ms;
                <span class="keyword">var</span> speed = upload.received - rq; rq = upload.received;
                <span class="keyword">var</span> remaining = (upload.size - upload.received) / speed;
                <span class="keyword">var</span> uReceived = parseInt(upload.received) + <span class="string">' bytes'</span>;
                <span class="keyword">var</span> uDiff = parseInt(diff) + <span class="string">' bytes'</span>;
                <span class="keyword">var</span> tTotal = parseInt(elapsed + remaining) + <span class="string">' secs'</span>;
                <span class="keyword">var</span> tElapsed = parseInt(elapsed) + <span class="string">' secs'</span>;
                <span class="keyword">var</span> tRemaining = parseInt(remaining) + <span class="string">' secs'</span>;
                <span class="keyword">var</span> percent = Math.round(<span class="number">100</span>*rate) + <span class="string">'%'</span>;
                <span class="keyword">var</span> uSpeed = speed + <span class="string">' bytes/sec'</span>;
                document.getElementById(<span class="string">'length'</span>).firstChild.nodeValue = parseInt(upload.size) + <span class="string">' bytes'</span>;
                document.getElementById(<span class="string">'sent'</span>).firstChild.nodeValue = uReceived;
                document.getElementById(<span class="string">'offset'</span>).firstChild.nodeValue = uDiff;
                document.getElementById(<span class="string">'total'</span>).firstChild.nodeValue = tTotal;
                document.getElementById(<span class="string">'elapsed'</span>).firstChild.nodeValue = tElapsed;
                document.getElementById(<span class="string">'remaining'</span>).firstChild.nodeValue = tRemaining;
                document.getElementById(<span class="string">'speed'</span>).firstChild.nodeValue = uSpeed;
                document.getElementById(<span class="string">'bar'</span>).firstChild.nodeValue = percent;
                document.getElementById(<span class="string">'bar'</span>).style.width = percent
              }
              <span class="keyword">else</span> {
                window.clearTimeout(interval);
              }
            }
          }
        }
        fetch.send(<span class="literal">null</span>);
      }
    </span><span class="tag"></<span class="keyword">script</span>></span>
  <span class="tag"></<span class="keyword">head</span>></span>
  <span class="tag"><<span class="keyword">body</span>></span>
    <span class="tag"><<span class="keyword">form</span><span class="attribute"> method=<span class="value">"post"</span></span><span class="attribute"> enctype=<span class="value">"multipart/form-data"</span></span><span class="attribute"> id=<span class="value">"upload"</span></span><span class="attribute"> onsubmit=<span class="value">"progress();"</span></span>></span>
      <span class="tag"><<span class="keyword">input</span><span class="attribute"> type=<span class="value">"hidden"</span></span><span class="attribute"> id=<span class="value">"count"</span></span><span class="attribute"> value=<span class="value">"1"</span></span> /></span>
      <span class="tag"><<span class="keyword">div</span><span class="attribute"> id=<span class="value">"multiple"</span></span>></span>
        <span class="tag"><<span class="keyword">input</span><span class="attribute"> type=<span class="value">"file"</span></span><span class="attribute"> name=<span class="value">"file[]"</span></span><span class="attribute"> multiple</span> /></span><span class="tag"><<span class="keyword">br</span>></span>
      <span class="tag"></<span class="keyword">div</span>></span>
      <span class="tag"><<span class="keyword">input</span><span class="attribute"> type=<span class="value">"submit"</span></span>></span>
      <span class="tag"><<span class="keyword">a</span><span class="attribute"> href=<span class="value">"#"</span></span><span class="attribute"> onclick=<span class="value">"add();"</span></span>></span>add();<span class="tag"></<span class="keyword">a</span>></span>
    <span class="tag"></<span class="keyword">form</span>></span>
    <span class="tag"><<span class="keyword">div</span><span class="attribute"> id=<span class="value">"status"</span></span><span class="attribute"> style=<span class="value">"display: none;"</span></span>></span>
      <span class="tag"><<span class="keyword">table</span><span class="attribute"> width=<span class="value">"100%"</span></span>></span> 
        <span class="tag"><<span class="keyword">tr</span>></span><span class="tag"><<span class="keyword">th</span>></span><span class="tag"></<span class="keyword">th</span>></span><span class="tag"><<span class="keyword">th</span>></span>загрузка<span class="tag"></<span class="keyword">th</span>></span><span class="tag"><<span class="keyword">th</span>></span>осталось<span class="tag"></<span class="keyword">th</span>></span><span class="tag"><<span class="keyword">th</span>></span>всего<span class="tag"></<span class="keyword">th</span>></span><span class="tag"></<span class="keyword">tr</span>></span>
        <span class="tag"><<span class="keyword">tr</span>></span><span class="tag"><<span class="keyword">td</span>></span>время:<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"elapsed"</span></span>></span>∞<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"remaining"</span></span>></span>∞<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"total"</span></span>></span>∞<span class="tag"></<span class="keyword">td</span>></span><span class="tag"></<span class="keyword">tr</span>></span>
        <span class="tag"><<span class="keyword">tr</span>></span><span class="tag"><<span class="keyword">td</span>></span>размер:<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"sent"</span></span>></span>0 b<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"offset"</span></span>></span>0 b<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"length"</span></span>></span>0 b<span class="tag"></<span class="keyword">td</span>></span><span class="tag"></<span class="keyword">tr</span>></span>
        <span class="tag"><<span class="keyword">tr</span>></span><span class="tag"><<span class="keyword">td</span>></span>скорость:<span class="tag"></<span class="keyword">td</span>></span><span class="tag"><<span class="keyword">td</span><span class="attribute"> id=<span class="value">"speed"</span></span>></span>n/a<span class="tag"></<span class="keyword">td</span>></span><span class="tag"></<span class="keyword">tr</span>></span>
      <span class="tag"></<span class="keyword">table</span>></span>
      <span class="tag"><<span class="keyword">div</span><span class="attribute"> style=<span class="value">"border: 1px solid #c0c0c0;"</span></span>></span>
        <span class="tag"><<span class="keyword">div</span><span class="attribute"> style=<span class="value">"background: #c0c0c0; width: 0%; text-align: right;"</span></span><span class="attribute"> id=<span class="value">"bar"</span></span>></span>0%<span class="tag"></<span class="keyword">div</span>></span>
      <span class="tag"></<span class="keyword">div</span>></span>
      <span class="tag"><<span class="keyword">a</span><span class="attribute"> href=<span class="value">"#"</span></span><span class="attribute"> onclick=<span class="value">"if (confirm('Вы точно хотите отменить загрузку?')) window.location = '/'"</span></span><span class="attribute"> id=<span class="value">"cancel"</span></span>></span>cancel_upload();<span class="tag"></<span class="keyword">a</span>></span>
    <span class="tag"></<span class="keyword">div</span>></span>
  <span class="tag"></<span class="keyword">body</span>></span>
<span class="tag"></<span class="keyword">html</span>></span>

EOF

Переходим на http://localhost/index.html, пробуем загрузить файлы… Прогресс-бар работает! Теперь дело за малым, написать сам бэкенд, который будет выполняться уже после загрузки файлов на сервер и управлять ими, и нарисовать какой-никакой дизайн для файлообменника.
И с этим, пожалуй, думаю вы справитесь сами, так как моей целью было лишь показать серверную реализацию загрузки файлов с информированием о самом процессе загрузки.

Автор: Spoofing
http://www.pvsm.ru/nginx/11481

Запись опубликована в рубрике *HTTP, *Web. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Я не спамер This plugin created by Alexei91