Как организовать пересылку файла из клиентского компьютера на сервер (исследование конкретного случая).

Однажды я обнаружил, что для разработки API (программный интерфейс приложения) необходимо загружать файлы от клиента на сервер. Я работаю на русский почтовый провайдер Mail.ru и мне приходится иметь дело со всеми аспектами JavaScript. Основной особенностью любого почтового веб-сервиса конечно же является возможность присоединения файла к электронному письму.

Mail.ru не является исключением: мы используем Flash Uploader, который довольно хорошо себя зарекомендовал, но все же иногда возникают некоторые проблемы. В структуру этого загрузчика входит HTML разметка, графика, бизнес-логика и даже локализация. Все эти компоненты сделали загрузчик довольно функциональным, но в то же время чрезмерно раздутым. Кроме того, любые изменения может вносить только Flash разработчик. Мы поняли, что существует необходимость создать что-то новое и необычное. В данной статье рассматриваются все наши шаги в создании того, что мы считаем лучшим инструментом для работы.

Любой, кто когда-либо работал с Flash Uploader знает, какие проблемы могут возникнуть:

  • Куки-файлами для аутентификации довольно трудно управлять поскольку в зависимости от браузера и операционной системы во Flash у них наблюдается странное поведение (куки-файлы не могут совместно использоваться с запросами HTTP и FileReference выгрузкой/выгрузкой). Официально Flash поддерживает куки-файлы только в IE, и они не будут совместно использоваться с другими браузерами, или же будут извлечены из IE;
  • Есть предположения, что с Flash куки-файлы считываются из Internet Explorer, хотя эта версия официально не подтверждена;
  • Настройки прокси (модуль доступа к интернету) довольно неудобно обновлять; с использованием Flash эти настройки всегда извлекаются из IE, независимо от используемого браузера;
  • Ошибки # 2038 и # 2048, незаметные ошибки, которые появляются в некоторых комбинациях параметров сети, браузера и некоторых версий Flash Player;
  • AdBlock и т.п. (без комментариев).

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

  • Возможность выбора нескольких файлов;
  • Возможность получения информации о файле (название, тип);
  • Возможность предварительного просмотра изображений перед загрузкой;
  • Возможность изменения размера, обрезка и поворот изображения клиентом;
  • Возможность загрузки результатов на сервер, а также CORS;
  • Независимость от внешних библиотек;
  • Возможность расширения функциональных возможностей.

За последние четыре года мы прочитали множество горячих обсуждений различных функций и опций HTML5, в том числе файловый API. Многие публикации затрагивают этот тип API, и у нас есть несколько примеров его функционирования. Можно было бы подумать: «Вот тот инструмент, который поможет решить нашу проблему». Но так ли всё просто, как кажется на первый взгляд?

Давайте посмотрим на браузерную статистику для Mail.ru. Мы выбрали только те версии браузеров, которые поддерживают File API, хотя в некоторых случаях эти браузеры не обеспечивают полную поддержку API.

The browsers used support File API

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

  • Chrome 10
  • Firefox 3.6
  • Opera 11.10
  • Safari 5.4
  • IE 10

Кроме того, мы не должны забывать о мобильных браузерах, которые с каждым днем становятся все более и более популярными. Возьмем, например, IOS 6, который уже поддерживает файловую API. Как бы там ни было, но 87% — это не 100%, а в нашем случае не возможно полностью отказаться от использования Flash.

Итак, наша задача превратилась в необходимость создания инструмента, который сочетал бы в себе две технологии (файловую API и Flash), что позволило бы разработчику не акцентировать внимание на способы загрузки файлов. В процессе разработки мы решили объединить все предварительные наработки в отдельную библиотеку (унифицированный API), которая будет работать полностью независимо от окружающей среды и может использоваться где угодно, а не только в нашем сервисе.

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

Определение списка файлов.

Рассмотрим основные моменты. Вот как файлы определяются в HTML5. Всё очень просто.

<input id="file" type="file" multiple="multiple" />
<script>
      var input = document.getElementById("file");
      input.addEventListener("change", function (){
           var files = input.files;
      }, false);
</script>

Но что же делать, если у вас есть только поддержка Flash и нет поддержки файловой API? Основная идея, которую мы стремились реализовать для пользователей с поддержкой Flash, заключалась в совершении всех взаимодействий через Flash. Вы не можете просто взять и вызвать диалоговое окно выбора файла. В связи с политикой безопасности диалоговое окно будет открываться только после того, когда произойдёт клик по флэш-объекту.

По этой причине флэш-объекты будут располагаться выше целевых входных элементов. Затем к документу нужно будет добавить mouseover обработчик событий, и поставить флэш-объект в родительский входной элемент, когда пользователь наводит над ним курсор.

Пользователь кликнет по флэш-объекту, откроет диалоговое окно выбора файла и выберет файл. Благодаря использованию функции ExternalInterface данные будут переданы из Flash в JavaScript. JavaScript свяжет полученные данные с входным элементом и имитирует событие change.

[[Flash]] --> jsFunc([{
     id: "346515436346", // unique identifier
     name: "hello-world.png", // file name
     type: "image/png", // mime-type
     size: 43325 // file size
   }, {
     // etc.
 }])

Все дальнейшие взаимодействия между JavaScript и Flash осуществляются через единственную доступную во Flash функцию. Первым аргументом является имя команды. Вторым параметром является объект с двумя обязательными полями: файловый идентификатор id и функция callback. Как только команда будет выполнена из Flash вызывается callback.

flash.cmd("imageTransform", {
    id: "346515436346",    // file identification
    matrix: { },    // transformation matrix
    callback: "__UNIQ_NAME__"
});

Сочетание этих двух методов позволяет создать API, который очень похож на стандартный функционал JavaScript. Единственное отличие состоит в том, каким способом были получены файлы. Теперь мы используем метод API, потому что вход имеет свойство files только при наличии браузера, который поддерживает HTML5 и файловую API. В случае Flash, список составляется из связанных с ним данных.

<span class="js-fileapi-wrapper" style="position: relative">
    <input id="file" type="file" multiple />
</span>
<script>
    var input = document.getElementById("file");
    FileAPI.event.on(input, "change", function (){
        var files = FileAPI.getFiles(input);
    });
</script>

Фильтр.

Как правило, загрузка файлов сопровождается набором ограничений. Наиболее распространенным является ограничение по размеру файла, типу изображений и геометрическим размерам (ширина и высота). Если вы познакомитесь с существующей информацией о возможных решениях этой проблемы, то узнаете, что проверка, как правило, выполняется на сервере, а если файл выходит за рамки каких-либо ограничений, то пользователь получает сообщение об ошибке. Я попытался подойти к решению этой проблемы немного с другой стороны и решил выполнять проверку файлов у клиента — до начала передачи файла на сервер.

В чем же загвоздка? Загвоздка в том, что когда мы первоначально получаем список файлов, у нас есть только минимальная информация: имя, размер и тип. Для получения более подробной информации, мы должны фактически считать файлы. Чтобы сделать это, мы можем использовать FileReader.

Итак, если поэкспериментировать с FileReader, то можно создать следующий способ фильтрации:

FileAPI.filterFiles(files, function (file, info){
    if( /^image/.test(file.type) ){
        return info.width > 320 && info.height > 240;
    } else if( file.size ){
        return file.size < 10 * FileAPI.MB;
     } else {
         // Unfortunately, there is no support for File API or Flash. We have to validate on the server.
         // This case is rather rare, but we must consider it as part of the project.
         return true;
     }
 }, function (files, ignore){
     if( files.length > 0 ){
        // ...
    }
});

Вы можете получить размеры файла, а также все необходимые вам данные:

FileAPI.addInfoReader(/^audio/, function (file, callback){
    // collect required information
    // and call it back
    callback(
        false,    // or error message
        { artist: "...", album: "...", title: "...", ... }
    );
});

Обработка изображений

При создании API мы хотели получить удобный и мощный инструмент, который бы позволил создать максимально комфортные условия работы с изображениями — например, организация предварительного просмотра, обрезка, вращение и масштабирование — и функциональные возможности которого будут поддерживаться как в HTML5, так и во Flash.

Flash.

Во-первых, мы должны понять, как это всё сделать с помощью Flash — то есть, что нужно отправить в JavaScript для построения изображения. Наверное, все знают, что обычно это делается с использованием URI (унифицированный идентификатор ресурса) данных. Flash считывает файл как Base64 (код преобразования текстовых символов в 6-битные двоичные числа) и передает его в JavaScript. Нужно добавить data:image/png;base64 и использовать эту строку как src.

Насколько успешным был результат? К сожалению, IE 6 и 7 версии не поддерживает данные URI, а IE 8 версии, который поддерживает данные URI, не может обрабатывать более 32 килобайт. В этом случае, JavaScript создаст второй Flash объект и передаст в него закодированное в Base64 содержимое. Этот Flash объект восстановит изображение.

HTML5.

В случае HTML5 мы хотели сначала получить оригинальное изображение, а затем выполнить все необходимые преобразования, используя холст. Получение исходного изображения можно организовать одним из двух способов. Первый способ заключается в чтении файла как данных URI с использованием FileReader. Второй способ заключается в использовании функции URL.createObjectURL для создания ссылку на файл, который привязан к текущей вкладке. Стоит отметить, что второго способа вполне достаточно, чтобы организовать предварительный просмотр, но проблема заключается в том, что не все браузеры его поддерживают. Например, Opera 12 не поддерживает функцию URL.revokeObjectURL, которая информирует браузер о том, что больше нет необходимости сохранять ссылку на файл.

Когда мы объединим все эти методы, то получим класс FileAPI.Image:

  • crop(x, y, width, height)
  • resize(width,[height])
  • rotate(deg)
  • preview(width, height) — обрезание и изменение размеров изображения
  • get(callback) — использовать окончательный вариант изображения

Все эти методы позволяют заполнить матрицу преобразования, которая применяется только тогда, когда вызывается функция get() ...

Если вы хотите прочитать полностью статью, посетите сайт наших спонсоров

Comments are closed.