2018年7月31日火曜日

multer-s3を使ってaws S3へファイルアップロードする時の注意点(Node.js)


オフィス狛 技術部のHammarです。

今回もまた開発をしていく最中にちょっとハマったことについて書こうと思います。
今回は「ファイルアップロード」についてです。
よくあるファイルアップロードの機能ですが、ある条件のときにちょっとしたことなんだけどうまくいかずにハマりました。
まずファイルアップロード機能実装でやろうとしたことと、その環境をザックリ簡単に記します。

■やりたいこと

・AWS S3に動画ファイルをアップロード
・アップロードしたファイルをwebサイトで視聴できるようにする
・バックエンドのnode.jsにS3アップロードのAPIを作成する

■実装環境

・node.js(Express)
・multer-s3モジュールを利用

とりあえずS3にファイルアップロードするためのaws-sdkをインストールしたり、アップロード用のモジュールをもろもろインストールしますが、今回のハマったところはこの先なのでこの辺のインストール方法等は省略します。

さて、上記環境は整っている状態で実際にアップロード処理を作成します。
アップロード処理について今回は「multer-s3」というモジュールをつかって下記のように処理を書きました。 npmjs.comのサンプルの記載とほぼ同じ感じです。

■S3アップロード処理サンプル

var aws = require('aws-sdk')
var express = require('express')
var multer = require('multer')
var multerS3 = require('multer-s3')

var app = express()
var s3 = new aws.S3({ /* ... */ })

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    metadata: function (req, file, cb) {
      cb(null, {fieldName: file.fieldname});
    },
    key: function (req, file, cb) {
      cb(null, ${req.body.fileName})
    }
  })
})

app.post('/upload', upload.array('photos', 3), function(req, res, next) {
  res.send('Successfully uploaded ' + req.files.length + ' files!')
})
上記をroutesディレクトリに任意のファイルを作成して記述します。

ここまでで基本的なアップロード機能は完成です。
で、実際に動かしてみます。今回アップロードしたときにそのファイル名とファイルIDも使いたかったので、requestのbodyにファイルとファイル名、ファイルのIDもつけて上記のAPIになげます。
動作の確認方法はいろいろあると思いますが、個人的にPostmanというツールを使ったほうが楽なのでこちらを使います。

■postmanでリクエスト作成

上記のような感じでリクエストを作成します。
  ①POSTを選択しAPIのURLを入力
  ②ファイルをアップロードするので「form-data」を選択
  ③アップロードするファイルと同時にリクエストするフィールド(key、value)を入力
  ④右上のsendボタンを押下
これで先ほど作成したAPIにリクエストが送られ、ファイルがアップロードされます。

で、ここからがハマったポイント
S3にファイルが上がっているか確認したところファイルは問題なく上がっています。ただ、なぜか②で一緒にbodyに記載していたファイル名やファイルIDがとれていない。そのためS3にアップロードしたファイル名が「undefined」となっています。アップロード自体はエラーとなっていませんでした。

    key: function (req, file, cb) {
      cb(null, ${req.body.fileName})
    }
上記のfileNameの部分でS3に上がったときのファイル名となる予定でしたが、なぜかfileNameがうまく取得できていないようです。
おかしい、同じbodyに入力していて、ファイルはアップロードできているのにファイル名がとれないなんて。。。

いろいろ調べてやってみても原因がわからず。。。
そこで最初のファイルがうまくいっているなら1番目の項目だけうまくいくのかと思い順番を下記のようにかえて再度アップロード。

なんと、ファイル名取れました!
ファイル自体もアップロードできている!
1番目も2番目の項目もうまくっているので1番目の項目だけってことではなさそうだけど、あれ?こんどはファイルIDが取れていない。
ということは、アップロードファイルより上の項目は取得できて、アップロードファイルより下の項目は取得できていないようです。

再度bodyの順番を下記のように並び替えてアップロードしてみます。
うまくいきました!
今度はファイル名、ファイルIDもちゃんと取れていました。

ということで、ファイルアップロード時のrequestのbodyはアップロードファイルの順番を最後にしておかないと、その他の項目が取得できないということがわかりました。

恐らく「multer-s3」というモジュールの特性や、記述の仕方もあるかもしれませんが、「multer-s3」でS3にアップロードするという実装に関しては上記のようなことが発生しますので注意が必要です。

またほかのnpmモジュールを使う際も少し注意したほうがよさそうで、「busboy」というモジュールも処理の組み方次第では同様の事象に注意が必要なようです。
multipartのアップロードストリームをYoutube投稿ストリームに受け流す

, ,

2018年7月8日日曜日

SVGの色をCSS上で変えてしまう方法


こんにちは、オフィス狛デザイン部のSatoです。

先日、アイコンをSVGで作成した際に、マウスオーバーすると色が変わったりするようなことがしたいなぁ……と思うことがありました。
普段なら商用フリーでCDNのアイコンフォントをアイコンにしてしまったりするのですが、今回は少し特殊なアイコンでそれもできず……。
SVGの色をテキストのようにCSSで変えたりできないものかと思って調べたらできる様子!
試してみた所、なんとか自分でもできたのでやり方をメモしたいと思います。

やり方は簡単。<svg>か親要素のCSSにfill: カラーコード;を追加するだけです。
ちなみに値を「currentColor」にするとなんとcolor設定されている色になります。
HTML
<a href="#"><svg>(本来はここにインラインで記載)</svg>Button</a>
CSS
a{color: #3ab28f;}
a:active{color: #3ddaa3;}
svg{fill: currentColor;}
こんな感じですね。
ただし、<img>タグや<object>タグではこの方法では色を読み込んでくれないようです。
最初自分はそこで躓いたのでご注意ください。

正直、自分は今までSVGのことを「どんなに拡大しても画像が荒れないすごい画像形式」くらいにしか思っていなかったのですがこんな事もできるんですね!びっくりしました。


,

2018年7月2日月曜日

Amazon S3 で サイト公開(HTTPS)する。(3)問い合わせフォーム準備編その2


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

前回は、Webサイトの問い合わせフォームから問い合わせされた内容をS3バケットへテキスト形式で保存するところまで行いました。
今回は、S3に保存されたら、その内容をメールで送信したいと思います。

※メールを送信する為のAWS SES(Simple Email Service)については説明を省きます。SESの設定が終わった状態で下記の手順を行って下さい。

1)ポリシー(Policy)の作成

まずは、AWSコンソール上のIAMのページへ遷移し、左側のメニューから「Policies」を選択。
その後、左上の「Create policy」をクリックします。


Serviceは、「CloudWatch Logs」
Actionは、「CreateLogStream」、「CreateLogGroup」、「PutLogEvents」
Resourceは「all resources」を選択して、
右下の「Review policy」をクリックします。


最後にNameとDescriptionを入力し、「Create policy」を選択します。


作成に成功しました。


2)ロール(Role)の作成

続いてロールを作成していきます。先程と同じくAWSコンソール上のIAMのページへ遷移し、
左側のメニューから「Roles」を選択します。


Select type of trusted entityは「AWS service」を選択。
Choose the service that will use this roleは「Lambda」を選択して、
右下の「Next: Permissions」をクリックします。


まずはFilterで「Policy type」を選択します。
続いてAttach permissions policiesでは「AmazonSESFullAccess」をチェックします。
(検索ボックスで「SES」と入力すると、フィルターが掛かって選択が楽です)


次にFilterで「Customer managed」を選択し、先程作成したポリシーにチェックを入れます。
(検索ボックスの中身を消さないと一覧に出て来ないのでご注意を)
右下の「Next: Review」をクリックします。


最後、Role nameを入力して、右下の「Create role」をクリックします。


作成に成功しました。


3)Lambda Functionの作成

AWSコンソール上のLambdaのページへ遷移し、「Create a function」を選択。


「Author from scratch」を選択し、Name等の必要な情報を入力します。
Runtimeは「Node.js」を選択して下さい。
Roleは「Choose an existing role」を選択し、Existing roleは、先程作成したロールを選択します。


作成直後の状態です。
ここで、左側の「Add triggers」から、「S3」を選択します。


対象のバケットを選択し、Event typeは「PUT」を選択します。 「Add」をクリックします。

続いて、Node.jsのプログラムを貼り付けます。


貼り付けるプログラムは以下の通りです。
'use strict';

console.log('Loading function');

const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const ses = new aws.SES({ apiVersion: '2010-12-01', region: 'us-east-1' });

exports.handler = (event, context, callback) => {
    const bucket = event.Records[0].s3.bucket.name;
    const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
    const params = {
        Bucket: bucket,
        Key: key
    };

    s3.getObject(params, (err, data) => {
        if (err) {
            console.log(err);
            const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
            console.log(message);
            callback(message);
        } else {
            const json = JSON.parse(data.Body.toString());
            const message = `
                名前: ${json.name}           
                MAIL: ${json.email}
                問い合わせ日時:
                ${json.date}
                問い合わせ内容:
                ${json.comments}
                `
            const params = {
                Destination: {
                    ToAddresses: [process.env.TOADDRESS]
                },
                Message: {
                    Body: {
                        Text: {
                            Charset: "UTF-8",
                            Data: message
                        }
                    },
                    Subject: {
                        Charset: "UTF-8",
                        Data: "フォームからのお問い合わせ"
                    }
                },
                Source: process.env.SOURCEADDRESS
            };
            ses.sendEmail(params, function (err, data) {
                if (err) console.log(err, err.stack);
                else console.log(data);
            });
        }
    });
};

プログラムに環境変数を使っているので、値を設定します。
SOURCEADDRESSが送信元(=from)アドレスで、
TOADDRESSが送信先(=to)アドレスです。


以上でLambdaの設定が完了となります。
Lambda作成後、前回作成した問い合わせフォームから、問い合わせを行うと・・・
この後、S3側のトリガー設定などが残っているのですが、以降は、独自ドメインの設定を行った後にやって行こうと思います。
と言う事で、WebサイトへのアクセスはS3のURLを利用しているので、 次回はRoute53を使用して、独自ドメインでの運用を可能にしていきたいと思います。

次回:Amazon S3 で サイト公開(HTTPS)する。(4)Route 53を使用した独自ドメイン運用
前回:Amazon S3 で サイト公開(HTTPS)する。(3)問い合わせフォーム準備編その1


, , ,