【Node.js】【AngularJS】 別ドメインのURL形式の画像ファイルまとめてzipでダウンロードする

要件

別のドメインの画像URLから画像を取得し、zpi形式でダウンロードしたい。

こんな感じ。(twitterの画像URL(https://pbs.twimg.com/~.jpg)をlocalhost上から画像として取得して、zipでDL)

XMLHttpRequestとか使ってクライアントだけで完結させようとすると、Cross Origin Resource Sharing Blockedに引っかかる。

No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin 'http://127.0.0.1:9001’ is therefore not allowed access.

なので、サーバーサイド(Node.js)側で画像をURLからbase64に変換。それをクライアントに譲渡し、zipに固めてダウンロードさせる流れにした。


使ったライブラリとかソース

  • JSZip – JavaScriptを使って、ファイルをZipで固めてダウンロードできる。
  • FileSaver.js – JSZipの公式サンプルで使っていたから、流用した。saveAs()でブラウザのダウンロードインターフェースを利用できるようになる。


ダウンロード

$ bower i jszip file-server -S


開発構成

  • AngularJS v1.3
  • Node.js + Express v4


流れ


コード

処理に関係のない部分は大分端折ってる。


user pageのzipアイコンをクリックすると、

【Client】user.jade

#page-content-wrapper
  i.fa.fa-file-archive-o(data-posts="{{userCategoryPosts}}", download-zip='download-zip')  {{userCategoryPosts.length}}


そのクリックイベントを受けて、HTTPリクエストを送信。

返ってきたデータ(画像URLからbase64に変換された文字列)を、JSZipをなんやかんやして固めて、saveAs()でダウンロード。

【Client】directive.js

angular.module('myApp.directives', [])
  .directive('downloadZip', [ 'toaster', 'DownloadService', function (toaster, DownloadService) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        element.on('click', function(event) {

          var postsParsed = JSON.parse(attrs.posts);

          // zipFolderの名前を定義しておく
          var tag = (postsParsed[0].tags.split(","))[2];
          var userName = postsParsed[0].userName;
          var userScreenName = postsParsed[0].userScreenName;
          var zipFolderName = "【" + tag + "】 " + userName + " 【@" + userScreenName + "】.zip";

          // DL開始をAngular-toasterで通知
          toaster.pop('wait', "Now Zip Downloading ...", '', 3000, 'trustedHtml');

          DownloadService.zip(postsParsed)
            .success(function(data) {

              // zipに固める 
              var zip = new JSZip();
              _.each(data.data, function(file){
                zip.file(file.name + '.jpg', file.image, {base64: true});
              });
              var content = zip.generate({type:"blob"});
              saveAs(content, zipFolderName);

              // DL終了を通知
              toaster.pop('success', "Finished Download", '', 3000, 'trustedHtml');
            });
        });
      }
    };
  }]);


HTTPリクエストの内容はservice.jsに書いてある。見たまんま。

【Client】service.js

angular.module('myApp.services', [])
  .service('DownloadService', function($http) {
    return {
      zip: function(posts) {
        return  $http.post('/api/downloadZip', {posts: posts});
      }
    };
  })


Node.js側で、requestモジュール使ってURLから画像ファイルを取得し、それをbase64に変換。変換処理のコードはこれを参考にした。

複数の画像URLに対して行い、全て変換が済んでからresponseを返す必要があるため、シーケンシャルに処理していかなければならない。今回はPromise.allを利用した。

【Server】api.js

exports.downloadZip = function(req, res) {

  var loadBase64Image = function (url) {
    return new Promise(function(resolve, reject) {
      request({
          url: url
        , encoding: null
      }, function (err, res, body) {
        if (!err && res.statusCode == 200) {
          var base64prefix = 'data:' + res.headers['content-type'] + ';base64,';
          var image = body.toString('base64');
          return resolve(image + base64prefix);
        } else {
          return reject(err);
          throw new Error('Can not download image');
        }
      });
    });
  };

  var tasks = [];
  _.each(req.body.posts, function(post){
    tasks.push(
      new Promise(function(resolve, reject) {
        loadBase64Image(post.sourceOrigUrl + '?.jpg')
        .then(function(imageBase64) {
          return resolve({
              image: imageBase64
            , name: post.tweetIdStr
          });
        })
        .catch(function(error) {
          return resolve('');
        });
      })
    );
  });

  Promise.all(tasks)
  .then(function(base64Array) {
    base64ArrayCompacted = _.pick(base64Array, _.identity);
    res.json({
      data: base64ArrayCompacted
    });
  });
}

参考文献

JSZip

Node.jsエンジニアなら2014年内に知っておきたいPromise入門 | Tokyo Otaku Mode Blog

node.js - get image from another domain and encode base64 by node js - Stack Overflow

Recent Posts

Loading...
blog comments powered by Disqus