Своё приложение на Node.js с хранением в Dropbox – это просто

    Несмотря на то, что главным моим хобби так и остаются роботы, я трачу немало усилий, чтобы оставаться в трендах своей основной стези – программирования. Волей судьбы недавно удалось познакомиться с Node.js, я узнал о его web фреймворке express, подружился с новым для себя template engine Jade и в довершение ко всему связал все это с папкой в Dropbox.
    image
    В этом посте я постараюсь коротко рассказать, как можно организовать web-сервис для хранения файлов, используя лишь бесплатные решения.
    Всех заинтересованных – прошу под кат.

    Подготовим плацдарм


    Итак, нам понадобится:
    • Node.js установленный на локальной машине
    • Аккаунт в Dropbox
    • Сервер Node.js приложений (если захочется запустить сервис не только локально)

    Если с первыми двумя пунктами все должно быть понятно, то на третьем мне бы хотелось остановиться чуть подробнее. Я уже упоминал, что все должно получиться бесплатно, и не собираюсь отступать от своих слов.
    В процессе моего “барахтанья” в Node.js мире, я наткнулся на целый ряд платформ готовых предоставить в наше распоряжение Node.js server бесплатно. Лично я испытывал две из них: Heroku и Nodester. В результате я все же остановился на втором, хотя, честно сказать, это решение ничем не обосновано.
    Для регистрации в Nodester необходимо получить купон. Сделать это можно на их сайте или в командной строке через nodester-cli. Мне купон пришел на следующий день после отправления запроса. Это очень быстро, хотя я не исключаю, что мне просто повезло.

    Создадим проект


    Локально

    С чего-то ведь надо начинать. Для этого создадим в любом удобном для нас месте папку (у меня называется habr-nodebox) и в ней файл package.json:

    {
      "name": "habr-nodebox",
      "version": "0.0.1",
      "node": "0.6.17",
      "author": "andbas",
      "dependencies": {
        "express": "2.5.x",
        "jade": "0.26.x",
        "dbox": "0.4.x"
      }
    }
    

    Поля name, version, author – просто дают некоторую информацию о проекте и могут быть изменены без каких-либо проблем; node – версия Node.js используемая в проекте; в секции dependencies перечисляются все используемые сторонние модули. Как я уже упоминал, в проекте будет использоваться express и jade. Плагин dbox, как понятно из названия, будет использоваться для работы с Dropbox. Я пробовал и другой плагин под названием dropbox, но он, к сожалению, не позволял авторизовать приложение, так как в нем был реализован старый API Dropbox, в котором использовался /token. На данный момент для аутентификации Dropbox использует стандарт oauth.
    После сохранения этого файла в командной строке вызовем:

    npm install    
    

    Если все было написано правильно, то npm скачает все упомянутые в dependencies модули и установит их в текущую директорию.
    Помимо этого, создадим еще две папки public и view. Первая будет использоваться для статических файлов (CSS, JS и других), в то время как view будет использоваться для шаблонов Jade.

    Тем временем в Dropbox

    Если мы хотим получить возможность складывать какие-то файлы в Dropbox, нам необходимо выполнить несколько действий, первым из которых будет получить ключ и секретную строку для нашего приложения. Для этого зайдем на страницу приложений нашего Dropbox аккаунта через браузер и создадим там новое приложение. В поле Access type устанавливаем значение App folder (приложение будет иметь ограниченный доступ только к собственной папке в Dropbox).
    На странице приложения запишем себе куда-нибудь App key и App Secret. Вот собственно первый шаг уже пройден.
    Для автоматизации последующих шагов в авторизации я предлагаю написать небольшой скрипт на все том же node.js. Скрипт следующий (dbox-init.js в папке нашего приложения):

    var dbox  = require("dbox"),
        stdin = process.stdin,
        stdout = process.stdout;
    
    ask('App key', /^\S+$/, function(app_key) {
      ask('App secret', /^\S+$/, function(app_secret) {
        var app = dbox.app({ 'app_key': app_key, 'app_secret': app_secret });
        app.request_token(function(status, request_token){
          if(request_token){
            console.log('Please visit ', request_token.authorize_url, ' to authorize your app.');
            ask('Is this done? (yes)', /^yes$/, function(answer) {
    
              app.access_token(request_token, function(status, access_token){
                console.log('app_key: ' + app_key);
                console.log('app_secret: ' + app_secret);
                console.log('oauth_token: ' + access_token.oauth_token);
                console.log('oauth_token_secret: ' + access_token.oauth_token_secret);
                console.log('uid: ' + access_token.uid);
                process.exit();
              });
            });
          }
        });
      });
    });
    
    function ask(question, format, callback) {
     stdin.resume();
     stdout.write(question + ": ");
    
     stdin.once('data', function(data) {
       data = data.toString().trim();
    
       if (format.test(data)) {
         callback(data);
       } else {
         stdout.write("It should match: "+ format +"\n");
         ask(question, format, callback);
       }
     });
    }
    

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

    node dbox-init
    

    Скрипт проходит вместе с вами все стадии oauth аутентификации в Dropbox и помогает получить все ключи необходимые нам. Шаги следующие:
    • скрипт запрашивает App key и App Secret (те, которые мы получили ранее) и генерирует на их основании ссылку
    • мы копируем ссылку в браузер и авторизуем приложение на работу с нашей учетной записью dropbox, получаем уведомление, что приложение авторизовано
    • на вопрос скрипта о том, прошли ли мы авторизацию смело пишем “yes”
    • получаем и записываем в укромное место данные необходимые для авторизации, а конкретно: app_key, app_secret, oauth_token, oauth_token_secret и uid

    На этом подготовительные работы пройдены, можем перейти к написанию самого приложения.

    В бой: набросаем контроллер


    Переходим, на мой взгляд, к самому интересному – написание контроллера. Основные действия такие: просматривать все файлы, добавлять новые, получать уже существующие. Не буду томить и сразу предоставлю код (web.js).

    var express = require('express'),
        app = express.createServer(express.logger()),
        fs = require('fs'), 
        dropbox  = require("dbox").app({"app_key": process.env['dbox_app_key'], "app_secret": process.env['dbox_app_secret'] }),
        client = dropbox.createClient({oauth_token_secret: process.env['dbox_oauth_token_secret'], oauth_token: process.env['dbox_oauth_token'], uid: process.env['dbox_uid']});
    
    app.use(express.static(__dirname+'/public'));
    app.set('views', __dirname + '/views');
    app.set('view engine', 'jade');
    app.set('view options', { layout: false });
    app.use(express.bodyParser());
    
    app.get('/', function(req, res) {
        client.metadata(".", function(status, reply) {
            res.render('index', {
                content : reply
            });
        });
    });
    
    app.get('/:path', function(req, res) {
        var path = req.params.path;
        client.get(path, function(status, reply, metadata){
          res.send(reply);
        }); 
    });
    
    app.post('/', function(req, res) {
        var fileMeta = req.files['file-input'];
        if (fileMeta) {
            fs.readFile(fileMeta.path, function(err, data) {
                if (err) throw err;
                
                client.put(fileMeta.name, data, function(status, reply) {
                    res.redirect('/');
                });
            });
        } else {
            res.redirect('/');
        }
    });
    
    var port = process.env['app_port'] || 5000;
    app.listen(port, function() {
        console.log("Listening on " + port);
    });
    

    Я думаю немного разъяснений, что же здесь происходит, не помешает.

    var express = require('express'),
        app = express.createServer(express.logger()),
        fs = require('fs'), 
        dropbox  = require("dbox").app({"app_key": process.env['dbox_app_key'], "app_secret": process.env['dbox_app_secret'] }),
        client = dropbox.createClient({oauth_token_secret: process.env['dbox_oauth_token_secret'], oauth_token: process.env['dbox_oauth_token'], uid: process.env['dbox_uid']});
    

    Объявление основных кирпичиков нашего приложения:
    • express – как уже упоминалось ранее, web framework
    • app – это собственно само web приложение
    • fs – интерфейс для работы с файловой системой
    • dropbox – фабрика для создания клиента Dropbox
    • client – клиент Dropbox упрощающий работу с API

    app.use(express.static(__dirname+'/public'));
    app.set('views', __dirname + '/views');
    app.set('view engine', 'jade');
    app.set('view options', { layout: false });
    app.use(express.bodyParser());
    

    Инициализация нашего web приложения. Здесь задаем основные параметры: пути к статическим файлам (public), директорию для шаблонов (views), движок этих самых шаблонов (jade), отключаем основной layout, чтобы немного упростить написание и обойтись одним шаблоном, и, в конце концов, передаем нашему приложению bodyParser, который будет разбирать тело приходящих запросов.
    А теперь перейдем к главной магии. Далее будут следовать три основных обработчика нашего сервиса.

    app.get('/', function(req, res) {
        client.metadata(".", function(status, reply) {
            res.render('index', {
                content : reply
            });
        });
    });
    

    Этот код отвечает за главную страницу. На ней мы будем отображать список файлов в нашей папке Dropbox, поэтому делаем запрос через client и получаем метаинформацию. Именно она содержит информацию обо всех файлах в нашем приложении. Эту информацию мы передаем нашему движку шаблонов, который и займется рендерингом страницы.

    app.get('/:path', function(req, res) {
        var path = req.params.path;
        client.get(path, function(status, reply, metadata){
          res.send(reply);
        }); 
    });
    

    Метод, написанный выше, будет обрабатывать запросы на получение файлов. Все предельно просто – получаем имя файла и отправляем запрос в Dropbox. Полученный ответ перенаправляем пользователю.

    app.post('/', function(req, res) {
        var fileMeta = req.files['file-input'];
        if (fileMeta) {
            fs.readFile(fileMeta.path, function(err, data) {
                if (err) throw err;
                
                client.put(fileMeta.name, data, function(status, reply) {
                    res.redirect('/');
                });
            });
        } else {
            res.redirect('/');
        }
    });
    

    Практически всю работу за нас сделал express. Файл, отправленный в теле post запроса на сервер, был временно сохранен на файловую систему. Нам была представлена вся необходимая для нас информация в объекте req.files['file-input'], где file-input – это атрибут name элемента input формы в html. Нам остается только взять файл из файловой системы и отправить в Dropbox. После этого мы будем перенаправлены на главную страницу.

    var port = process.env['app_port'] || 5000;
    app.listen(port, function() {
        console.log("Listening on " + port);
    });
    

    В конце мы устанавливаем порт для нашего приложения, значение по умолчанию будет 5000. Осталось только написать одну простую страничку, используя jade шаблон, этим и займемся.

    Jade шаблон – все дело в отступах


    Первое знакомство с Jade для меня лично было болезненным. Обычный html как-то ближе.
    Однако дискомфорт быстро прошел. Вторая страничка была написана уже без неприязни. В общем, привыкаешь быстро. Рекомендую попробовать.
    Подискутировать на тему удобства jade это конечно здорово, но пора и код показать. Для этого создадим в папке views файл index.jade и в нем пишем:

    !!! 5
    html(lang="en")
      head
        title habr-nodebox 
      body
        each item, i in content.contents
          div
            a(href="#{item.path}") #{item.path} - #{item.size}
        div 
          form#upload-form(action="/", method="POST", enctype="multipart/form-data")
            input#file-input(name="file-input", type="file")
            input#submit(value="Upload file", type="submit")    
    

    Я постарался сделать шаблон наиболее прозрачным и понятным. Конечно, добиться при этом выдающихся стилистических результатов не получится, но чем не пожертвуешь ради понятности.
    Мы лишь создали простейшую HTML страничку со списком файлов в нашей папке. Для этого мы прошлись в цикле each по всем записям о файлах. Напомню, метаданные content были заботливо получены для нашего шаблона в контроллере. Каждый элемент списка – это ссылка, ведущая нас к методу контроллера, для запросов вида “/:path ”. После списка следует форма для загрузки новых файлов. Она состоит из двух input элементов, один для файла, второй для отправки формы.

    Запуск


    Вот собственно наше приложение и готово, осталось только его запустить. Возможно кто-то обратил внимание, когда читал, что все ключи от Dropbox были записаны как переменные массива process.env[]. Сделано это для того, чтобы не оставлять их в коде и иметь возможность опубликовать приложение без страха быть скомпрометированным. Для того чтобы передать в массив process.env свои значения, достаточно записать их в виде key=value перед вызовом node. В командной строке должно получиться что-то вида:
    $ dbox_app_key=abc1qwe2rty3asd dbox_app_secret=123asd123asd123 dbox_oauth_token_secret=aaabbbccc111222 dbox_oauth_token=123asd123asd123 dbox_uid=12345678 node web 
    

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


    Вот собственно и все, если у кого-то есть желание запустить подобный код online – просто загрузите его на любой Node.js сервер. Я испытывал на Nodester – все работало, думаю, с остальными тоже не возникнет проблем.
    Код можно посмотреть здесь

    Похожие публикации

    Средняя зарплата в IT

    120 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 8 965 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

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

      0
      Если не секрет, для чего делал? Или просто «Hello node.js world»?
        0
        Да, не секрет вовсе. Нужен был быстрый способ зарендерить шрифты в браузере с возможностью отправить ссылку на страничку с результатом. Решил быстренько набросать на Node..js (как раз в этот момент разбирался с ним). Ну, а в итоге — думаю почему бы и не описать часть того приложения, на мой взгляд самую интересную.

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

      Самое читаемое