tag:crieit.net,2005:https://crieit.net/users/okumura_daiki/feed
オクムラダイキの投稿 - Crieit
Crieitでユーザーオクムラダイキによる最近の投稿
2018-12-26T17:34:10+09:00
https://crieit.net/users/okumura_daiki/feed
tag:crieit.net,2005:PublicArticle/14695
2018-12-26T14:22:38+09:00
2018-12-26T17:34:10+09:00
https://crieit.net/posts/Lambda-Edge
Lambda@Edgeで画像をリアルタイムにリサイズできる仕組みを作った
<p>今作成中のサービスで画像をリアルタイム変換できる仕組みを AWS の Lambda@Edge を用いて実現してみたので紹介します。</p>
<p>参考:<a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/">https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/</a></p>
<h2 id="Lambda@Edge とは"><a href="#Lambda%40Edge+%E3%81%A8%E3%81%AF">Lambda@Edge とは</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-edge.html">Lambda@Edge</a> とは、一言で言うと CloudFront (CDN) の HTTP リクエスト・レスポンスを操作できる Lambda 関数です。</p>
<p>下記のタイミングで CloudFront リクエスト・レスポンスを変更することができます。<br />
* CloudFront がビューワーからリクエストを受信した後 (ビューワーリクエスト)<br />
* CloudFront がリクエストをオリジンサーバーに転送する前 (オリジンリクエスト)<br />
* CloudFront がオリジンからレスポンスを受信した後 (オリジンレスポンス)<br />
* CloudFront がビューワーにレスポンスを転送する前 (ビューワーレスポンス)</p>
<p><a href="https://crieit.now.sh/upload_images/47b4cad07dd3d07a60ea9ee3d38d59665c23022c30410.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/47b4cad07dd3d07a60ea9ee3d38d59665c23022c30410.png?mw=700" alt="a.png" /></a></p>
<h2 id="作った仕組み"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E4%BB%95%E7%B5%84%E3%81%BF">作った仕組み</a></h2>
<p>CloudFront のオリジンサーバーに S3 を指定し、S3 からの画像レスポンスを Lambda@Edge で変換する仕組みを構築しました。</p>
<p>URLのパラメータを変えることでリサイズされた画像を取得できます。<br />
例えば下記のように幅と高さを指定してリクエストするとそれぞれのサイズの画像が返却されるようにしています。</p>
<p>・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=100x100 → 100x100の画像<br />
・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=300x300 → 300x300の画像<br />
・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=1000x1000 → 許可しないサイズはエラー</p>
<h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2>
<p>いろいろ説明をはしょってコードだけ貼っときます。。</p>
<h3 id="ビューワーリクエスト"><a href="#%E3%83%93%E3%83%A5%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">ビューワーリクエスト</a></h3>
<pre><code>'use strict';
const querystring = require('querystring');
const allowedDimensions = [ {w: 100, h: 100}, {w: 300, h: 300} ];
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
// URIが特定の画像パスに一致するかチェック(特定の画像以外はクエリストリングを削除してリクエスト)
const match = request.uri.match(/\/images\/user\/\d+\/avatar\/\d+\.jpg/);
if (match == null) {
request.querystring = '';
callback(null, request);
return;
}
// クエリストリングを key-value のペアにパース
const params = querystring.parse(request.querystring);
// dimensionパラメータがない場合、クエリストリングを削除してリクエスト
if (!params.d) {
request.querystring = '';
callback(null, request);
return;
}
// dimensionパラメータを'x'で分割
const dimensionMatch = params.d.split('x');
const width = dimensionMatch[0];
const height = dimensionMatch[1];
// dimensionパラメータが、許可リストに一致するかチェック
let matchFound = false;
for (let dimension of allowedDimensions) {
if (width == dimension.w && height == dimension.h) {
matchFound = true;
break;
}
}
// 許可リストにない場合、クエリストリングを削除してリクエスト
if (!matchFound) {
request.querystring = '';
callback(null, request);
return;
}
// クエリストリングにd={width}x{height}を設定してリクエスト
request.querystring = querystring.stringify({ d: width + 'x' + height });
callback(null, request);
};
</code></pre>
<h3 id="オリジンレスポンス"><a href="#%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%B3%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9">オリジンレスポンス</a></h3>
<pre><code>'use strict';
const querystring = require('querystring');
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
const Sharp = require('sharp');
const BUCKET = 'XXXXXXXX';
exports.handler = (event, context, callback) => {
let response = event.Records[0].cf.response;
if (response.status == 200) {
const request = event.Records[0].cf.request;
// クエリストリングを key-value のペアにパース
const params = querystring.parse(request.querystring);
// dimensionパラメータがない場合、そのままレスポンスを返す
if (!params.d) {
callback(null, response);
return;
}
// S3キー用にuriの最初のスラッシュを除いた文字列を取得
const key = request.uri.substring(1);
// dimensionパラメータを'x'で分割
const dimensionMatch = params.d.split("x");
const width = parseInt(dimensionMatch[0], 10);
const height = parseInt(dimensionMatch[1], 10);
// S3から画像を読み込み
S3.getObject({ Bucket: BUCKET, Key: key }).promise()
// リサイズ
.then(data => Sharp(data.Body)
.resize(width, height)
.toBuffer()
)
.then(buffer => {
// レスポンスを生成
response.status = 200;
response.body = buffer.toString('base64');
response.bodyEncoding = 'base64';
response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/jpeg' }];
callback(null, response);
})
.catch( err => {
callback(null, response);
});
} else {
// 200以外のステータスコードの場合、そのままレスポンスを返す
callback(null, response);
}
};
</code></pre>
<h2 id="なぜ作ったか"><a href="#%E3%81%AA%E3%81%9C%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B">なぜ作ったか</a></h2>
<p>普通なら「非同期ジョブでリサイズ処理してS3に複数サイズ保存」とかで事足りるのではと思います。</p>
<p>今回は、<br />
* アップロード時間を短くしたい(アップロード時はバリデートのみで変換処理しない)<br />
* 画像をアップロードした直後の画面でリサイズされた画像を表示したい<br />
* リサイズ画像をキャッシュしたい<br />
* 画像サイズあとで変えたいかも</p>
<p>といった理由で、CDNを通してリアルタイム変換という選択肢を選びました。</p>
<h2 id="感想"><a href="#%E6%84%9F%E6%83%B3">感想</a></h2>
<p>AWS Lambda 自体初めて触りましたが、サーバレスは便利だな〜と感じました。<br />
ただ、Lambdaの開発やテストやデプロイをどうやるのが正解なのかよくわからず、追加開発があるような比較的規模の大きいプロジェクトだとLambdaは大変そう(?)と思いました。</p>
<p>今回は画像リサイズだけでしたが、透かしを入れたり合成したり、画像以外にも動的にキャッシュできるので、いろいろ使い所ありそうです。</p>
<p>早くサービスリリースできるよう頑張ります。。</p>
オクムラダイキ
tag:crieit.net,2005:PublicArticle/14631
2018-12-07T05:00:34+09:00
2018-12-08T00:26:47+09:00
https://crieit.net/posts/e57cae523755c87e7464d21995705f50
アプリの使い方をわかりやすくしようと頑張った話
<h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2018/kuso-app2">クソアプリ2 Advent Calendar 2018</a>で「<a target="_blank" rel="nofollow noopener" href="https://okmr-d.github.io/fake-notification-web-app/">通知止まらんw</a>」というWebアプリを作ったのですが、最初出来上がった時にうちの奥さんに見せたら「使い方がわからない」と言われたので、操作方法の説明をもう少しわかりやすくしようと思って自分なりに直してみたって話を書きます。</p>
<p>作ったアプリはメディアに取り上げてもらったりちょっとだけ話題になることができたのですが、<br />
これをやってなかったらもしかしたらあまり使ってもらえなかったかもな、と今になって思ったので、大したことない内容ですが修正前後を比較して紹介します。</p>
<h2 id="こう操作して欲しい"><a href="#%E3%81%93%E3%81%86%E6%93%8D%E4%BD%9C%E3%81%97%E3%81%A6%E6%AC%B2%E3%81%97%E3%81%84">こう操作して欲しい</a></h2>
<p>① 全画面表示するためにWebアプリをホーム画面に追加してから開き直して欲しいので、Safariのメニューから「ホーム画面に追加」して!<br />
② メニューを開くボタンを「ロックアイコン」にしたのでそこを押して!<br />
③ メニューからモードを選んで、ツイートを入力したら始まるよ!</p>
<h2 id="修正前"><a href="#%E4%BF%AE%E6%AD%A3%E5%89%8D">修正前</a></h2>
<p><a href="https://crieit.now.sh/upload_images/dffd9ec2c5a24a0255945d2abe9c53995c095a87ea947.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/dffd9ec2c5a24a0255945d2abe9c53995c095a87ea947.png?mw=700" alt="修正前" /></a></p>
<p><em>これ読めばわかるっしょ!</em></p>
<h3 id="使ってもらった結果"><a href="#%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%82%E3%82%89%E3%81%A3%E3%81%9F%E7%B5%90%E6%9E%9C">使ってもらった結果</a></h3>
<p>嫁「使い方①がわからない。どうやってホーム画面に追加するの?」<br />
僕「下のここ(四角形から上向き矢印がつき出てるアイコンを指差しながら)がSafariのメニューだよ」<br />
嫁「ああ、やっぱりこれね」「・・・・・で?」<br />
僕「『ホーム画面に追加』ってのがあるから探して押して」<br />
嫁「あ、『ホーム画面に追加』あった。」「できた。Webサイトもアプリになるんだね。」</p>
<p><em>文章だけではわかりにくかったようだ!</em></p>
<p>②③はすんなりいったので大丈夫そう。</p>
<h3 id="わかったこと"><a href="#%E3%82%8F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">わかったこと</a></h3>
<ul>
<li>『Safariのメニュー』って言われても下のアイコン(四角形から上向き矢印がつき出てるやつ)で正しいのか分からない。(開いても右にスクロールしないと「ホーム画面に追加」は表示されないし)</li>
<li>『「ホーム画面に追加」してね』って言われてもどうやって?(そもそもホームに追加できるんだ!)って思うようだ。</li>
</ul>
<h2 id="修正した ver.1"><a href="#%E4%BF%AE%E6%AD%A3%E3%81%97%E3%81%9F+ver.1">修正した ver.1</a></h2>
<p><a href="https://crieit.now.sh/upload_images/5ad4ef4656cd9a4637819ccfb9b9fda95c096501927be.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ad4ef4656cd9a4637819ccfb9b9fda95c096501927be.png?mw=700" alt="修正した ver.1" /></a><br />
左:初期表示<br />
右:吹き出し閉じたあと</p>
<h3 id="主に直したところ"><a href="#%E4%B8%BB%E3%81%AB%E7%9B%B4%E3%81%97%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D">主に直したところ</a></h3>
<ul>
<li>説明読んで欲しいのでモーダルにした。</li>
<li>Safariのメニューの場所を吹き出しの形で指し示した。</li>
<li>押して欲しいアイコンを画像付きで表示した。</li>
<li>『「ホーム画面に追加」して』という表現を『(アイコン画像)を押して「ホーム画面に追加」して』と具体的にやって欲しい操作を書くようにした。</li>
<li>①の説明が難しいので②の説明を取った。</li>
<li>モーダル閉じたあとに②がわかるよう「←メニュー」って書いた</li>
</ul>
<h3 id="使ってもらった結果"><a href="#%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%82%E3%82%89%E3%81%A3%E3%81%9F%E7%B5%90%E6%9E%9C">使ってもらった結果</a></h3>
<p>嫁「2回目だからわかるわ」<br />
僕「初めての気持ちで」</p>
<p>嫁(使ってみてから)「たぶん大丈夫」</p>
<p><em>おっしゃー公開しよ〜</em></p>
<h2 id="公開した"><a href="#%E5%85%AC%E9%96%8B%E3%81%97%E3%81%9F">公開した</a></h2>
<p>が、</p>
<h3 id="Twitterアプリのこと考えてなかった"><a href="#Twitter%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E3%81%93%E3%81%A8%E8%80%83%E3%81%88%E3%81%A6%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F">Twitterアプリのこと考えてなかった</a></h3>
<p>Twitterでシェアした後にツイートのリンクを開いたらこんな画面だった</p>
<p><a href="https://crieit.now.sh/upload_images/d419689cc0c88b834841adadccee1f3d5c096a7106337.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d419689cc0c88b834841adadccee1f3d5c096a7106337.png?mw=700" alt="Twitterでシェアした後" /></a></p>
<p><strong>吹き出しの位置がアイコンとずれてる</strong>し、Twitterアプリからじゃ <strong>「ホーム画面に追加」が出ない!</strong></p>
<p>もう公開してシェアしちゃったし、簡単な修正で済ませたい!</p>
<h2 id="修正した ver.2"><a href="#%E4%BF%AE%E6%AD%A3%E3%81%97%E3%81%9F+ver.2">修正した ver.2</a></h2>
<p><a href="https://crieit.now.sh/upload_images/4265d25c518d8a7cc8c09e08cee449ce5c096e080ff5a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4265d25c518d8a7cc8c09e08cee449ce5c096e080ff5a.png?mw=700" alt="修正した ver.2" /></a></p>
<p>赤枠の文章を追加!(超最低限の修正)</p>
<p>ちゃんとやるならUserAgentを判定して、Twitterアプリだったら文章と吹き出しの位置を変えたらよかったな。。</p>
<h2 id="その他"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96">その他</a></h2>
<h3 id="シェアして欲しかったからこんな修正もした"><a href="#%E3%82%B7%E3%82%A7%E3%82%A2%E3%81%97%E3%81%A6%E6%AC%B2%E3%81%97%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%8B%E3%82%89%E3%81%93%E3%82%93%E3%81%AA%E4%BF%AE%E6%AD%A3%E3%82%82%E3%81%97%E3%81%9F">シェアして欲しかったからこんな修正もした</a></h3>
<p><a href="https://crieit.now.sh/upload_images/80473bfefcbe7cf0b82be5ade5e466cf5c09718b0a78d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/80473bfefcbe7cf0b82be5ade5e466cf5c09718b0a78d.png?mw=700" alt="メニューも修正した" /></a><br />
左:修正前<br />
右:修正後</p>
<ul>
<li><p>「録画した画面をシェアして楽しもう!」と促した<br />
録画できるんだ、シェアしてみよう、と思った人が若干は増えたと思う(想像)</p></li>
<li><p>「画面を録画する方法」のリンクを貼った<br />
画面を録画する機能を使ったことない人が大多数なので、これつけなかったらシェアしてくれる数結構減ったと思う(想像)</p></li>
<li><p>「#通知止まらんw」をつけてツイートしてね!<br />
は要らなかったかも。画面録画したツイートでつけてくれてる人少なかった。<br />
(自分で入力するの面倒だし。画面録画をシェアするときはみんなに本当の通知画面だと思わせたいから、いきなりタグつけてタネあかししたくないかも。)</p></li>
<li><p>「このサイトをシェア」リンク<br />
これはつけてよかった。そのままシェアしてくれた人がそこそこいた。</p></li>
</ul>
<h3 id="もっと直したかったところ"><a href="#%E3%82%82%E3%81%A3%E3%81%A8%E7%9B%B4%E3%81%97%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D">もっと直したかったところ</a></h3>
<p>ホーム画面に追加したアプリを開くと再度吹き出しモーダルが表示されてしまうので、本当は消したかった。</p>
<p>UserAgentと画面サイズから判定して全画面かどうかを判定するのがいいのだろうか?大変そうなので今回は諦めました。</p>
<h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2>
<p>以上、UI/UXやってるデザイナーさんから見たら下手な修正かもしれませんが、自分なりに改善した点をまとめてみました。</p>
<p>自分では「みんなわかるっしょ」と思っていても他の人からするとそうではないことがあるってことが改めてよくわかってよかったです(小並感)</p>
オクムラダイキ