- PVSM.RU - https://www.pvsm.ru -

Сервис загрузки файлов на Golang

В ходе разработки серверной части сервиса загрузки файлов на Golang родилось отдельное приложение – pavo [1]. В задачи приложения входит загрузка целых файлов, по одному или несколько за раз, кусочная загрузка файла(chunked upload), конвертер изображений. Реализована загрузка данных через multipart/form-data и загрузка файла в бинарном виде в теле запроса. Для работы в production окружении используется nginx для авторизации и обработки медленных соединений. В качестве клиентской библиотеки можно использовать jQuery File Uploader [2].

Установка

Установка компилятора

Для установки приложения необходим компилятор Golang. Инструкцию по его установке можно найти на официальном сайте [3]. Так же необходимо настроить [4] переменную окружения $GOPATH.

Для примера, как это можно сделать в MacOS:

  1. Устанавливаем компилятор;
    $ brew install go
    $ mkdir $HOME/go
  2. Настраиваем переменную окружения, отредактировав профайл пользователя.
    # Add this line in your .zshrc or .bash_profile
    export GOPATH=$HOME/go
    export PATH=$PATH:$GOPATH/bin
    
Установка систем контроля версиями

Репозитории с кодом в сообществе не централизованы. Используются различные системы контроля версий:

Пример установки в MacOS:

$ brew install git mercurial svn bazaar
Установка ImageMagick

Для конвертации изображений на сервер используется ImageMagick [9]:

$ brew install imagemagick
Установка приложения

При первой установке запустите команду в консоли:

$ go get github.com/kavkaz/pavo

При обновлении приложения и зависимых библиотек:

$ go get -u github.com/kavkaz/pavo/...

Быстрый старт

Для того, чтобы посмотреть, как работает приложение с базовым примером, запустим в консоли команду:

$ pavo --storage=$GOPATH/src/github.com/kavkaz/pavo/dummy/root_storage

Тем самым мы запустили приложение с корневой директорией в указанном через опцию --storage каталоге. Сервис c базовым примером будет доступен по адресу http://localhost:9073/example/jfu-basic.html. Для указания другого хоста и порта используйте консольную опцию --host.

Подробности протокола

Типовой ответ сервера при загрузке изображения:

{
    "files": [
        {
            "dir": "/image/2014/6s/1c5cnx",
            "name": "original_user_filename.jpg",
            "type": "image",
            "versions": {
                "original": {
                    "filename": "original-1qeh.jpg",
                    "height": 420,
                    "size": 28057,
                    "url": "/image/2014/6s/1c5cnx/original-1qeh.jpg",
                    "width": 300
                },
                "thumbnail": {
                    "filename": "thumbnail-1qef.jpg",
                    "height": 90,
                    "size": 3566,
                    "url": "/image/2014/6s/1c5cnx/thumbnail-1qef.jpg",
                    "width": 120
                }
            }
        }
    ],
    "status": "ok"
}

Самый распространённый способ загрузки файлов на сервер – использование форм. В таком случае запрос представляется следующим образом:

POST /files HTTP/1.1
Content-Length: 21929
Content-Type: multipart/form-data; boundary=----5XhQf4IXV9Q26uHM

------5XhQf4IXV9Q26uHM
Content-Disposition: form-data; name="files[]"; filename="pic.jpg"
Content-Type: image/jpeg

...bytes...

В заголовке Content-Type передаётся значение boundary, которое служит для разделения значений в теле запроса. Таким образом за один запрос можно передать несколько файлов. jQuery File Upload имеет соответствующую опцию [10], для множественной отправки файлов.

Современный подход позволяет отправлять бинарные данные, используя на клиенской стороне запрос типа XHR [11]. Запрос, который увидит сервер, выглядит следующим образом:

POST /files HTTP/1.1
Content-Length: 21744
Content-Disposition: attachment; filename="pic.jpg"

...bytes...

Таким способом можно передать за один запрос только один файл, имя которого будет доступно в заголовке Content-Disposition.

Для загрузки больших файлов на клиентской стороне формируется пачка запросов с частями исходного файла. Пример запроса:

POST /files HTTP/1.1
Content-Length: 10240
Content-Range: bytes 0-10239/36431
Content-Disposition: attachment; filename="pic.jpg"
Cookie:pavo=377cb76c-2538-40d3-a3d0-13d86d206ba7

...bytes...

Имя исходного файла и значение cookie по ключу pavo используется для идентификации промежуточного файла с загруженными частями оригинала. Заголовок Content-Range содержит информацию о том, какую часть файла предаёт клиент и каков размер оригинального файла. Если загружается последний кусок, то сервер завершает процедуру загрузки и формирует ответ с данными о полученном файле и его версиях.

Код приложения

Приложение написано на языке Golang. В качестве веб-фреймворка используется Gin [12]. Код разделен на два пакета(upload и attachment) и основое приложение (исполняемый файл). Пакет upload отвечает за загрузку исходных файлов или куска файла. Пакет attachment отвечает за создание конечной директории для хранения файла и его версий, конвертацию изображений, формирование данных. Основное приложение запускает веб-сервер и реализует роль контроллера.

Исходный код с небольшими примера в тестах доступен на github [1].

Опции приложения

Приложение имеет опции запуска --host и --storage. Указывают host:port для запуска веб сервера и корневую директорию хранилища соответственно.

Все запросы на загрузку приложение принимает на адрес /files. В query_string параметре converts можно передать параметры конвертации для изображений. Например:

POST /files?converts={"pic":"400x300"}

Для всех файлов устанавливается версия по умолчанию original. Для изображений добавляется thumbnail со значением 120x90.

Production environment

Для работы в production окружении желательно использовать веб сервер nginx [13]. В задачи веб сервера будет входить приём запросов от клиентов, запись тела во временный файл, авторизация запроса на основном приложении, отправка заголовков исходного запроса на приложение pavo.

Рекомендуемая конфигурация nginx:

server {
    listen 80;
    server_name pavo.local;
    
    access_log /usr/local/var/log/nginx/pavo/access.log;
    error_log /usr/local/var/log/nginx/pavo/error.log notice;
    
    location /auth {
        internal;
        proxy_pass http://localhost:3000/auth/url/in/your/app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_request_body off;
        proxy_set_header Content-Length 0;
        client_max_body_size 0;
    }

    location /files {
        auth_request /auth;
    
        client_body_temp_path     /tmp;
        client_body_in_file_only  on;
        client_body_buffer_size   521K;
        client_max_body_size      10G;
    
        proxy_pass_request_headers on;
        proxy_set_header X-FILE $request_body_file;
        proxy_pass http://127.0.0.1:9073;
    }
    
    location / {
        root /Path/To/Root/Of/Storage;
    }
}

От клиента на веб сервер приходит запрос. Nginx дожидается получения всего запроса (эта его особенность не позволяет реализовать полноценный progress bar с проксированием на сервер приложения). После получения запроса тело будет записано во временный файл в директорию, определенную опцией client_body_temp_path.

Перед отправкой запроса на сервер приложения pavo будет произведена авторизация. Для этого используется модуль ngx_http_auth_request_module [14]. Будет сделан подзапрос на location /auth, который в свою очередь проксирует заголовки исходного запроса на сервер основного приложения. В случае успешной авторизации сервер должен вернуть пустое тело с кодом статуса ответа 200.

Далее в заголовки исходного запроса добавляется новая пара, ключ X-File, а значение – путь до временного файла с телом запроса. И только после этого получившийся запрос (заголовки и пустое тело) отправляется на прилоежение pavo. Оно обрабатывает запрос, сохраняя файлы, и возвращает ответ с данными о загруженных файлах в JSON формате.

Заключение

Сервис задумывался как самостоятельное приложение в инфраструктуре веб проекта, которое берёт на себя роль загрузки и раздачи файлов, конвертации изображений, видео и аудио. С интерфейсом через HTTP Json API.

Автор: Kavkaz

Источник [15]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/nginx/68788

Ссылки в тексте:

[1] pavo: https://github.com/kavkaz/pavo

[2] jQuery File Uploader: https://github.com/blueimp/jQuery-File-Upload

[3] официальном сайте: https://golang.org/doc/install

[4] настроить: http://golang.org/doc/code.html#GOPATH

[5] Git: http://git-scm.com

[6] Mercurial: http://mercurial.selenic.com

[7] Subversion: https://subversion.apache.org

[8] Bazaar: http://bazaar.canonical.com/en/

[9] ImageMagick: http://www.imagemagick.org

[10] опцию: https://github.com/blueimp/jQuery-File-Upload/wiki/Options#singlefileuploads

[11] XHR: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

[12] Gin: https://github.com/gin-gonic/gin

[13] nginx: http://nginx.org

[14] ngx_http_auth_request_module: http://nginx.org/en/docs/http/ngx_http_auth_request_module.html

[15] Источник: http://habrahabr.ru/post/234693/