- 投稿日:2019-09-27T23:13:03+09:00
LamdaでS3に上がっている画像を一括でリサイズする(ローカルから実行可能)
目的
S3に上がっている画像ファイルをリサイズしたい
→ Webサイトの画像DLの負荷を下げる、画面表示の高速化をするLamdaの実装
Lamdaの基本的な設定は端折ります。
S3にアップされたファイルを別のバケットにリサイズして作成するAPIを以下の通り実装。!!同じバケットを指定すると無限実行されるので注意!!
index.js
index.jsvar AWS = require('aws-sdk'); var gm = require('gm').subClass({ imageMagick: true }); var util = require('util'); var s3 = new AWS.S3(); exports.handler = function(event, context, callback) { var bucket = event.Records[0].s3.bucket.name; // 変換元画像のバケット名 var key = event.Records[0].s3.object.key; // 変換元画像のキー名 var thumbnailBucket = bucket + "-thumbnail"; // 変換後画像のバケット名(ここでは末尾にthumbnailを付け足すことにしてある) var thumbnailKey = key; // 変換後画像のキー名(ここでは変換前と同じにしてある) // 拡張子を抽出 var typeMatch = key.match(/\.([^.]*)$/); if (!typeMatch) { callback("Could not determine the image type."); return; } // 拡張子がjpg, jpeg, pngのものだけ許可する var imageType = typeMatch[1]; if (imageType != "jpg" && imageType != "jpeg" && imageType != "png") { callback('Unsupported image type: ${imageType}'); return; } // S3の画像にアクセス s3.getObject({Bucket: bucket, Key: key}, function(err, data){ if (err) { context.done('error getting object', err); } // ----- Icon用 ------- gm(data.Body) .resize('40', null) // 40pxにする .stream(function(err,stdout,stderr){ // ストリームで出力する if(err){ context.fail(err); } var chunks = []; // ストリームのチャンクを溜め込むための配列 stdout.on('data',function(chunk){ chunks.push(chunk); // チャンクを溜め込む }); stdout.on('end',function(){ var buffer = Buffer.concat(chunks); // 最後まで溜め込んだら結合してバッファに書き出す gm(buffer).toBuffer(function(errs, buff){ var params = { Bucket: thumbnailBucket, Key: 'icon/'+thumbnailKey, ContentType: imageType, Body: buff }; s3.putObject(params, function(err, data) { // S3に書き出す if(err){ console.log("gm upload error"); context.fail(err); } context.succeed({ "error":false }); }); }); }); stderr.on('data',function(data){ console.log('stderr data: ' + data); }); }); // Icon Create End }); };gnu-sedのインストール
payload.jsonのファイル名を書き換えられるように以下インストール
$ brew install gnu-sedinvokeコマンドを連続実行するシェルスクリプトを書く
resize.shfilename=$1 cat ${filename} | while read line do gsed -i -e "30c\\ \"key\": \"${line}\"," payload.json aws lambda invoke --invocation-type Event --function-name {*lamdaName*} --region ap-northeast-1 --payload file:///Users/{userName}/develop/payload.json output.txt echo ${line} donepayload.jsonを作成する
payload.json{ "Records":[ { "eventVersion":"2.0", "eventSource":"aws:s3", "awsRegion":"ap-northeast-1", "eventTime":"1970-01-01T00:00:00.000Z", "eventName":"ObjectCreated:Put", "userIdentity":{ "principalId":"AIDAJDPLRKLG7UEXAMPLE" }, "requestParameters":{ "sourceIPAddress":"127.0.0.1" }, "responseElements":{ "x-amz-request-id":"C3D13FE58DE4C810", "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" }, "s3":{ "s3SchemaVersion":"1.0", "configurationId":"testConfigRule", "bucket":{ "name":"{バケット名}", "ownerIdentity":{ "principalId":"A3NL1KOZZKExample" }, "arn":"arn:aws:s3:::{バケット名}" }, "object":{ "key": "{対象ファイル名 *シェルで書き換えらる}", "size":1024, "eTag":"d41d8cd98f00b204e9800998ecf8427e", "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko" } } } ] }リサイズしたいファイルをtxtにまとめる
images.txtuser/file1.png user/file2.png user/file3.png実行コマンド
$ . resize.sh images.txt実行すると
202
のステータスが返ってきますが、キューに送られただけで正常なので大丈夫です。まずはLamda単体をテスト実行してから動かしていきましょう。
参考記事
- 投稿日:2019-09-27T20:48:42+09:00
jsのビルドで「Module not found: Error: Can't resolve 'regenerator-runtime/runtime'」が出た時の対策
「Module not found: Error: Can't resolve 'regenerator-runtime/runtime'」が出た
async/awaitを使っているコードをビルド(babelをかけたら)したら
以下のようなエラーとなる場合がある。Module not found: Error: Can't resolve 'regenerator-runtime/runtime' in xxx対策案その1 core-jsとregenerator-runtimeを導入する
Babel 7.4.0以降を使っているなら、
2つのnpmモジュールcore-jsとregenerator-runtimeをインストール
npm install core-js@3 regenerator-runtimeasync/awaitを使っているソースコード(.js)でいまインストールしたモジュールをimportする
import "core-js/stable"; import "regenerator-runtime/runtime"; ・・・以下略・・・対策案その2 @babel/polyfill を導入する
@babel/polyfillをインストール
npm install --save-dev @babel/polyfillasync/awaitを使っているソースコード(.js)で import "@babel/polyfill" する
import "@babel/polyfill"; ・・・以下略・・・babel公式によると、@babel/polyfillは Babel 7.4.0で deprecatedとなっているので注意
対策案その3 対象ブラウザを変更する
<変更前>古いブラウザを対象にした@babel/preset-envの設定
※targetsに注目
webpack.config.js抜粋{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { 'modules': 'false',//commonjs,amd,umd,systemjs,auto 'useBuiltIns': 'usage', 'targets': '> 0.25%, not dead', 'corejs': 3 } ] ] } } ], },<変更後>新しいブラウザを対象にした@babel/preset-envの設定
※targetsに注目
webpack.config.js抜粋{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { 'modules': 'false',//commonjs,amd,umd,systemjs,auto 'useBuiltIns': 'usage', "targets": { "browsers": [ "last 1 Chrome version", "last 1 Firefox version", "last 1 Edge version", "last 1 Safari version", "last 1 Opera version" ] }, 'corejs': 3 } ] ] } } ], },(対策その2のほうは、babelの目的から考えると、本末転倒かな。)
- 投稿日:2019-09-27T16:09:56+09:00
Libraのハッカソンにlibra-authというのを出してみた
Facebookの仮想通貨Libraが来年発行されるという事で、世界中の中央銀行はじめトランプさんなどからも袋叩きの今日この頃ですが、
まぁ、そういう政治的?というか大人の方々のお話はともかくとして、Libra使って何ができるのかいじってみたいというのはQiita界隈に生息するものの本能だったりもします。
で、先月そのLibraのデベロッパーグループの主催で行われたハッカソンHackLibraにLibra Authというプロジェクトで参加して、入賞は逃したけど「Honorable mention (名誉ある言及)」という賞的なものを頂いたので、その辺を少し書いてみようかなと思います。
最近Qiitaに全然書いてなかったし。
HackLibra
https://medium.com/@hacklibra/hacklibra-winners-8da1a9124628自分のブログ「かも日記」 LibraのハッカソンHackLibraで3位入賞はできなかったけどなんか褒められた感?
https://jsgt.org/wp/?p=975Libra Auth
https://github.com/toshirot/libra-auth残念ながら締め切り日の4日後にLibra testnet の更新があって、私のデモが依存していたライブラリが動かなくなってしまいました。
まぁ、発展途上のLibraなのでしょうがないのですが、その修正(修正しても良いかはちゃんと聞いたのだけど)が締め切り後ということで減点になってしまったので3位入賞を逃してしまいました。あれが無ければ入賞していたと自負はしてます。えへん。
さて、Libra Authという名前でわかる通り、これはLibraを利用したauthenticationについての話です。
Libraの公開鍵と秘密鍵を利用した認証方法で、EdDSAによる署名を使用します。
Libraクライアントの鍵に基付く認証ですが、BobとAlice間の認証時の通信はLibraブロックチェーンを呼び出さなくても可能(呼び出せばより強固ですが)なので、Libraブロックチェーンに負荷を与えません。というわけで処理速度が速いです。
以下の図はこのDemoのものですが、ユーザー「ALICE」はLibraでチケットセンター「BOB」にチケット料金を支払い、署名チケットで認証されます。
詳細は Github の説明を観ていただくとして、ざっくりいうと、Libraを送受信した「Alice」と「Bob」本人であることをお互いに証明できるPrivate Key とトランザクション上で参照できるPublic Key を利用して相互に本人確認し、Signatureで作ったQRコードで認証をする方法についてのテスト実装です。
このデモは、例えば入場券の販売などを想定しています。
手順
(1,2,3) AliceはBobのチケットセンターへ10Libra送金し、Bobへアドレスとメッセージを送ります。
(4,5) Bobは受け取ったメッセージと自分のPrivate Key でSignatureBを作りAliceへ送ります。
(6,7) Aliceは送ったメッセージと受け取ったSignatureBをBobのPublic Key でVerifyしてTrueならボブが、2のトランザクションでLibraを支払った相手であることが証明されます。
そこで、SignatureBとAliceのPrivate Key でSignatureAを作り、それとAliceのAddressでQRコードを作ります。
(8) Bobは入場ゲートでAliceのQRコードをスキャンし、送ったSignatureBと受け取ったSignatureAをAliceのPublic Key でVerifyしてTrueならAliceが、2のトランザクションでLibraをBobへ支払った人であることが証明されるので
(9) 証明された入場をOKにします。上記の(1~4)以降はLibraネットワークを参照しなくても認証は可能なので、例えば入場門で数万人を連続処理してもLibraのネットワークに負荷をかけないということです。
もちろん、QRコードの盗難や転売対策として、(5~8)を繰り返すことも可能です。あるいは、転売を許可する代わりに、転売手数料がBobにも入る仕組みにすることもできると思います。
デモ
ALICE: Buy ticket:
http://libra-auth.com/buy-ticket.html
BOB: Authenticate ticket:
https://libra-auth.com/auth-ticket.htmlコードはNode.jsで書いてますが、基本的にはどんな言語でもかけるかなと思います。その辺はこれから少しいじるつもりなので、後日続きを書きたいなと思ってます。現状のDemoコードはGithubでご覧ください。
- 投稿日:2019-09-27T09:17:14+09:00
【Azure Bot Service】QnA Makerのfollow-up promptに対応する
前提条件
Node.js の SDK v4 でBot Serviceを作っている。
QnA Maker のfollow-up promptに対応する
SDKの対応状況
QnA Maker には follow-up prompt という機能があるがpreview版なので、現在 Bot Service からダウンロードできるソースコードでは、まだ対応していない。
しかし、botbuilder-ai
のソースコードを見ると、ライブラリの方ではこっそり対応されている。https://github.com/microsoft/botbuilder-tools/issues/1272#issuecomment-525475729
上記リンクのコメントにもあるが、まだpreview版ということで、公式にはまだ対応したということになっていない。
StackOverflowに簡単なサンプルが載っている。
ただしC#で書かれていて、Node.jsで作っている場合は参考程度にしかならない。実装サンプル
まず、新たにjsファイルを作成し、QnA Makerから返ってきた回答にfollow-up promptが付いているか確認し、付いている場合は返答にボタンを追加する処理を作る。
qnaResultHelper.jsconst { MessageFactory } = require('botbuilder'); /** * QnA Maker から返ってきた答えを適切な形にしてクライアントへ返す * * @param {string} qnaResult QnA Maker から返ってきたanswer * @returns Activity */ module.exports.createAnswer = function(context, qnaResult) { if (qnaResult.context && qnaResult.context.prompts && qnaResult.context.prompts.length > 0) { // answerにfollow-up promptが付いている return createPrompt(qnaResult); } else { return qnaResult.answer; } } /** * follow-up prompt をクライアントへ返す * * @param {QnAMakerResult} qnaResult * @returns Activity */ function createPrompt(qnaResult) { let title = qnaResult.answer; let prompts = qnaResult.context.prompts; let buttons = []; // DisplayOrderで並べ替え prompts.sort(function(a, b) { if (a.displayOrder == b.displayOrder) { return 0; } else if (a.displayOrder < b.displayOrder) { return -1; } return 1; }); // follow-up promptのボタンを作る for (let prompt of prompts) { buttons.push(prompt.displayText); } return MessageFactory.suggestedActions(buttons, title); }次に、既存のqnaBot.jsの
onMessage
の処理を修正し、回答を返す部分で、先ほど作ったHelperクラスのメソッドを呼び出すようにする。qnaBot.jsthis.onMessage(async (context, next) => { this.logger.log('Calling QnA Maker'); const qnaResults = await this.qnaMaker.getAnswers(context); // If an answer was received from QnA Maker, send the answer back to the user. if (qnaResults[0]) { // 自作したHelperのメソッドを呼び出す let answer = QnAResultHelper.createAnswer(context, qnaResults[0]); await context.sendActivity(answer); // If no answers were returned from QnA Maker, reply with help. } else { await context.sendActivity('お答えできません。他の言い方で質問してみてください。'); } // By calling next() you ensure that the next BotHandler is run. await next(); });
- 投稿日:2019-09-27T04:14:29+09:00
Webpackで複数プロジェクトの一括コンパイルと部分コンパイル
インデックス
* ディレクトリ毎にアプリケーションをコンパイル
* プロジェクト共通のライブラリを設定
* コンパイルするターゲットを決めてビルドする要件
以下のようなディレクトリ構成があり、それぞれのプロジェクト毎にJavaScriptをコンパイルして、それぞれのプロジェクトディレクトリにコンパイルしたファイルを格納したいといった案件がありました。Webpackでプロジェクトをコンパイルする時、今までは一つのディレクトリにのみ全てのプロジェクトを書き出していたので、ディレクトリ単位に書き出す方法を調べました。また、すべてのプロジェクトではなく、特定のプロジェクトだけビルドする方法も合わせて調べた結果を記載します。ディレクトリ構造. ├── package.json ├── src │ ├── common <- プロジェクト共通で使うファイル │ └── projects │ ├── foo <- 単一プロジェクト │ ├── fuga <- 単一プロジェクト │ ├── hoge <- 単一プロジェクト │ └── pages <- スペシャルプロジェクト │ ├── pageOne <- スペシャルプロジェクトに関連するプロジェクト │ ├── pageThree <- スペシャルプロジェクトに関連するプロジェクト │ └── pageTwo <- スペシャルプロジェクトに関連するプロジェクト └── webpack.config.js
通常であれば、
webpack.config.js
のoutput
に以下のような設定をすると複数ページのアプリケーションとしてコンパイルしてくれます(参考)。特定のディレクトリに別アプリケーションとして書き出しmodule.exports = { //... entry: { pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' }, output: { filename: '[name].js', path: __dirname + '/dist' } };ディレクトリ毎にアプリケーションをコンパイル
今回は一つのディレクトリにまとめて書き出すのではなく、プロジェクトディレクトリ毎にそれぞれ書き出したい訳です。
それぞれのプロジェクトディレクトリに書き出し│ └── projects │ ├── foo │ │ ├── app.js │ │ ├── dist <- ここに書き出して管理したい │ ├── fuga │ │ ├── app.js │ │ └── dist <- ここに書き出して管理したい │ ├── hoge │ │ ├── app.js │ │ └── dist <- ここに書き出して管理したいそのため、少しトリッキーかもしれませんが、
entry
とoutput
の設定を変更し、
複数ディレクトリに書き出しできるような設定に変更しました。今回は各プロジェクトに distというフォルダを作成し、その中にコンパイル後のファイルを書き出します。イメージとしてはこんな感じです。イメージmodule.exports = { //... entry : { './src/projects/path-to-project/dist/hashfilename.js': './src/projects/path-to-project/app.js', }, output: { path: __dirname, filename: '[name]' } };
output.path
は書き出す先のパスを指定します。今回は書き出す先のディレクトリをルート直下に指定し、ファイル名にパスを含めた書き出し先を指定しました。outputで使っている[name]
はWebpack側で定義されている設定でentryのkeyとなっている部分を置換します。なので上記の設定だと ./src/projects/path-to-project/dist/hashfilename.js が[name]
に入ることになります。余談ですが、プロジェクト単位で作成されるdistディレクトリを
git
の管理から外す場合は.gitignore
に以下のように記述すると良いです。.gitignoreの設定/**/dist/プロジェクト共通のライブラリを設定
それぞれのプロジェクトで共通して使いたいクラスは以下の設定で使用可能になります。
ライブラリパスの設定module.exports = { //... resolve: { module: [ path.resolve(__dirname, './src/common'), ]; } };こうすると
./src/common
が名前空間になり、例えば./src/common/Common.js
のファイルをプロジェクトで使いたい場合、import文は以下の書式になります。import Common from 'Common';また、
./src/common/particle/flower.js
の場合はそのままサブディレクトリを繋げれば良いです。import Common from 'particle/flower';コンパイルするターゲットを決めてビルドする
複数プロジェクトのビルドに対応しておけば、1つのプロジェクトだけビルドしたい場合や、2つのプロジェクトだけビルドしたい場合など、特定のプロジェクトに絞ってビルドすることも可能になります。
方法はnpm run build
とする際に、webpack.config.js
に環境変数(env)としてビルドターゲットのプロジェクト名を渡します。こんな感じです。npm run build -- --env.target="hoge"このコマンドには2つの意味が含まれているのですが、
--env.target="hoge"
の部分がwebpack.config.jsに環境変数(env)を渡している部分となります。
また、webpack.config.jsで環境変数(env)を受け取る場合、エクスポートを関数に変更する必要があります。
https://webpack.js.org/guides/environment-variables/To use the env variable, you must convert module.exports to a function:
環境変数(env)の渡し方webpack --env.NODE_ENV=local --env.production --progress環境変数(env)の受け取り方const path = require('path'); module.exports = env => { // Use env.<YOUR VARIABLE> here: console.log('NODE_ENV: ', env.NODE_ENV); // 'local' console.log('Production: ', env.production); // true return { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } }; };私の場合は
env
にtarget
プロパティを追加し、target
にプロジェクトディレクトリがあれば、そのプロジェクトのみビルドするようにしました。なお、プロジェクトのファイルは全て統一してapp.js
しています。// NOTE: Use function in order to pass environment variables. // @see https://webpack.js.org/guides/environment-variables/ module.exports = (env, {watch} ) => { let entry = {}; if (env && env.target) { // NOTE: Divide target string into array by comma. const targetFiles = env.target.split(',').map(s => s.trim()) for (file of targetFiles) { // NOTE: ここでプロジェクト毎にentryを作成しています(割愛) const filePath = './' + join(targetDir, 'app.js'); const anEntry = createEntry(filePath); Object.assign(entry, anEntry); } } else { // NOTE: 全てのエントリー情報 entry = createAllEntry(); } return { //... entry: entry, output: { path: __dirname, filename: '[name]' }, } })ここでもうひとつ
npm run build -- --env.target="hoge"
実行時のbuildの後の--
についてですが、これのダブルハイフン以降に定義する変数を実行時のファイルに変数として渡すという意味です。例えば、以下のnpmパッケージがあり、package.json{ script: { server: "node index.js" } }以下のコマンドを実行すると、
index.js
に環境変数「--port=8080」が渡されます。npm run server -- --port=8080これは、通常の
node
コマンドを実行するときと同じ意味になります。node server.js --port=1337npmコマンドに渡す変数と、スクリプトに渡す変数の2つの違いがあるということですね。
Conclusion
今回は複数プロジェクトをビルドする時にプロジェクト毎にディレクトリを指定してビルドする方法と、プロジェクトの一部だけビルドする方法を調査しました。部分ビルドについてはプロジェクトが膨大になった時にビルド時間を短縮するために有効そうです。この部分ビルドは
watch
オプションをつけた時にも適用されるので、開発していくときに便利かもしれません。今回の検証で使ったプロジェクトはGihubに置いてありますので、何かの参考になれば幸いです。