hexoのテーマを変更する

前回までのまとめ

前回、ビジネス書の記事は別のドメイン(サブドメイン)にした方がよいだろうということで、新しいhexoアプリケーションのベースを作成するところまで行いました。

前回のまとめで、次回はnginxの設定を行いたいと書きましたが、今回はhexoのテーマを何にするか決めたいと思います。

hexoのテーマ

デフォルトのテーマ

hexoのテーマはデフォルトでlandscapeというテーマになっています。

https://hexojs.github.io/hexo-theme-landscape/

このサイトもlandscapeを使っています。せっかくなのでなにか新しいテーマにチャレンジしたいと思います。

テーマを探す

hexoのテーマはどういうものがあるのでしょうか。こちらにテーマの一覧がありました。たくさんあって目移りしてしまいます…

TOPの一覧がカードタイプになっているテーマがよかったので、Gradientというテーマにしました。

テーマを設定する

テーマを設定するには

  1. テーマをインストールする
  2. _config.ymlでテーマを指定する

でできそうです。

テーマをインストールする

先ほどのリンクのREADME.mdにインストールの方法が書かれています。

1
2
3
4
5
6
7
8
9
10
11
$ cd themes
$ git clone https://github.com/DeepSpaceHarbor/Gradient
Cloning into 'Gradient'...
remote: Enumerating objects: 127, done.
remote: Counting objects: 100% (127/127), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 653 (delta 64), reused 69 (delta 28), pack-reused 526
Receiving objects: 100% (653/653), 5.46 MiB | 1.62 MiB/s, done.
Resolving deltas: 100% (329/329), done.
$ ls
Gradient landscape

こちらで完了です。

_config.ymlでテーマを指定する

インストールしたテーマを利用するように設定します。

1
2
3
4
5
6
$ pwd
/business_blog/themes
$ cd ..
$ ls
_config.yml node_modules package.json source yarn.lock
db.json package-lock.json scaffolds themes

アプリケーショントップにある_config.ymlを修正します。

1
2
3
4
5
$ vi _config.yml
98 # Extensions
99 ## Plugins: https://hexo.io/plugins/
100 ## Themes: https://hexo.io/themes/
101 theme: landscape

こちらのthemeをGradientに変更して保存します。

以上で設定は完了です。

動作確認

テーマが正しく反映されたかどうか動作確認をします。

1
2
3
4
$ npx hexo server
INFO Validating config
INFO Start processing
INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.

ブラウザでhttp://localhost:4000にアクセスします。

hexoのテーマ Gradient

ポインタが変わったりするんですね…ちょっと他のも検討してみようと思います…

他のテーマを試す

Aomoriというテーマが見た目シンプルでよいなと思って試してみました。

動作確認でエラー

インストールと設定はいつも通り行なって動作確認をしてみると

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ERROR {
err: TypeError: /Users/user/github/business-book-reviews/business_blog/themes/aomori/layout/layout.ejs:20
18| <aside class="sidebar">
19| <% } %>
>> 20| <%- partial('_partial/sidebar') %>
21| </aside>
22| </div>
23| </div>

/Users/user/github/business-book-reviews/business_blog/themes/aomori/layout/_partial/sidebar.ejs:7
5|
6| <div class="widget" id="widget">
>> 7| <% config.aomori_widgets.forEach(function(widget){ %>
8| <%- partial('_widget/' + widget) %>
9| <% }) %>
10| </div>

Cannot read property 'forEach' of undefined
at eval (/Users/user/github/business-book-reviews/business_blog/themes/aomori/layout/_partial/sidebar.ejs:21:30)
at sidebar (/Users/user/github/business-book-reviews/business_blog/node_modules/ejs/lib/ejs.js:682:17)
at _View._compiledSync (/Users/user/github/business-book-reviews/business_blog/node_modules/hexo/lib/theme/view.js:132:24)
at _View.renderSync (/Users/user/github/business-book-reviews/business_blog/node_modules/hexo/lib/theme/view.js:59:25)
at Object.partial (/Users/user/github/business-book-reviews/business_blog/node_modules/hexo/lib/plugins/helper/partial.js:34:15)
at eval (/Users/user/github/business-book-reviews/business_blog/themes/aomori/layout/layout.ejs:28:17)
at layout (/Users/user/github/business-book-reviews/business_blog/node_modules/ejs/lib/ejs.js:682:17)
at _View._compiled (/Users/user/github/business-book-reviews/business_blog/node_modules/hexo/lib/theme/view.js:136:50)
at _View.render (/Users/user/github/business-book-reviews/business_blog/node_modules/hexo/lib/theme/view.js:39:17)
at /Users/user/github/business-book-reviews/business_blog/node_modules/hexo/lib/theme/view.js:51:25
at tryCatcher (/Users/user/github/business-book-reviews/business_blog/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/Users/user/github/business-book-reviews/business_blog/node_modules/bluebird/js/release/promise.js:547:31)
at Promise._settlePromise (/Users/user/github/business-book-reviews/business_blog/node_modules/bluebird/js/release/promise.js:604:18)
at Promise._settlePromise0 (/Users/user/github/business-book-reviews/business_blog/node_modules/bluebird/js/release/promise.js:649:10)
at Promise._settlePromises (/Users/user/github/business-book-reviews/business_blog/node_modules/bluebird/js/release/promise.js:729:18)
at _drainQueueStep (/Users/user/github/business-book-reviews/business_blog/node_modules/bluebird/js/release/async.js:93:12) {
path: '/Users/user/github/business-book-reviews/business_blog/themes/aomori/layout/layout.ejs'
}
} Render HTML failed: index.html

となって表示できません。エラーの内容を確認すると、Cannot read property 'forEach' of undefinedとなっていて、config.aomori_widgetsがundefinedになっているのが原因のようです。

エラーの修正

README.mdにあるようにthemes/_config.ymlaomori_widgetsを設定してみました。

1
2
3
4
5
6
aomori_widgets:
- toc # Article navigation
- category # Article classification
- tag # Article tags
- recent_posts # latest articles
- archive # Article Archive

しかし、エラーは消えません。READMEをよくみると、Configuration in the Global _config.ymlとなっていて、アプリケーション下の_config.ymlに設定しないといけないようです。

アプリケーション下の_config.ymlに設定してエラーはなくなりました。

hexoのテーマ aomori

しかし、テーマの設定でアプリケーション下の_config.ymlに設定するのは、よくない設計だと思います。

まとめと次回予告

いろいろテーマを探してみて、いろんなものがあるのだなぁと思いました。また、テーマの変更方法を理解することができました。

次回は今回疑問に思った各ディレクトリの_config.ymlの設定の違いについて調べたいと思います。

新しくhexoアプリケーションを作成する

背景

GoogleSearchConsoleを見ていると、ビジネス書の記事でインデックスされなくなっていたり、ビジネス書の記事でほとんど検索上位に表示されないということがわかりました。

たまたま読んだ沈黙のWebマーケティングによると、ジャンル・カテゴリが違う内容はサブドメインを切った方が良いという風に書かれていました。(この本は見た目はアレですが、片手間でしかSearchConsoleを使ったことがないぼくにとってはとても勉強になりました)

なので、今回新しくサブドメインを作成し、ビジネス書の記事はそちらに集約しようと思います。

hexoアプリケーションの作成

このブログはhexoでできていますが、作り方を忘れてしまったので確認しながら記載していきます。

nodejsのインストール

nodejsは現在LTS最新版が14.15.1です。これをインストールします。

1
2
3
4
5
6
7
8
$ nodenv install 14.15.1
node-build: definition not found: 14.15.1

See all available versions with `nodenv install --list'.

If the version you need is missing, try upgrading node-build:

git -C /Users/user/.anyenv/envs/nodenv/plugins/node-build pull

nodenvを更新します。anyenvを利用しているので、 anyenv updateします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ anyenv update
Updating 'anyenv'...
| From https://github.com/riywo/anyenv
| e963a69..67d402f master -> origin/master
| * [new tag] v1.1.2 -> v1.1.2
Updating 'anyenv/anyenv-git'...
Updating 'anyenv/anyenv-update'...
Updating 'nodenv'...
Updating 'nodenv/node-build'...
| From https://github.com/nodenv/node-build
| ddc0daa4..bb925ce4 master -> origin/master
| * [new tag] v4.9.19 -> v4.9.19
| * [new tag] v4.9.16 -> v4.9.16
| * [new tag] v4.9.17 -> v4.9.17
| * [new tag] v4.9.18 -> v4.9.18
Updating 'nodenv/nodenv-vars'...
Updating 'pyenv'...
| From https://github.com/pyenv/pyenv
| 806b30d6..943015eb master -> origin/master
Skipping 'pyenv/python-build'; not git repo
Updating 'rbenv'...
| From https://github.com/rbenv/rbenv
| c879cb0..60c9339 master -> origin/master
Updating 'rbenv/ruby-build'...
| From https://github.com/rbenv/ruby-build
| 1642176..f85906e master -> origin/master
| * [new tag] v20201118 -> v20201118
| * [new tag] v20200722 -> v20200722
| * [new tag] v20200727 -> v20200727
| * [new tag] v20200819 -> v20200819
| * [new tag] v20200926 -> v20200926
| * [new tag] v20201005 -> v20201005
| * [new tag] v20201117 -> v20201117
Updating 'anyenv manifest directory'...

updateが終わったのでインストールします。

1
2
3
4
5
$ nodenv install 14.15.1
Downloading node-v14.15.1-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v14.15.1/node-v14.15.1-darwin-x64.tar.gz
Installing node-v14.15.1-darwin-x64...
Installed node-v14.15.1-darwin-x64 to /Users/user/.anyenv/envs/nodenv/versions/14.15.1

インストールが終わったので、このバージョンを利用するようにします。

1
2
3
4
5
$ nodenv local 14.15.1
$ ls -a
. .. .node-version
$ cat .node-version
14.15.1

hexoのインストール

あとは9こちらに書かれている通り進めていきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ npm install -g hexo-cli
/Users/user/.anyenv/envs/nodenv/versions/14.15.1/bin/hexo -> /Users/user/.anyenv/envs/nodenv/versions/14.15.1/lib/node_modules/hexo-cli/bin/hexo
+ hexo-cli@4.2.0
added 64 packages from 338 contributors in 5.057s
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
<省略>
$ npm install hexo
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN business-book-reviews@1.0.0 No description

+ hexo@5.2.0
added 91 packages from 371 contributors and audited 91 packages in 4.774s

11 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

バージョン5.2.0がインストールされました。

インストール完了後は

1
$ npx hexo <command>

でコマンドが実行できます。

hexoのセットアップ

こちらを参考に進めていきます。

1
2
3
4
5
6
7
8
$ npx hexo init business_blog
INFO Cloning hexo-starter https://github.com/hexojs/hexo-starter.git
INFO Install dependencies
warning hexo-renderer-stylus > stylus > css-parse > css > urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
warning hexo-renderer-stylus > stylus > css-parse > css > source-map-resolve > urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
warning hexo-renderer-stylus > stylus > css-parse > css > source-map-resolve > resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
warning Your current version of Yarn is out of date. The latest version is "1.22.5", while you're on "1.22.4".
INFO Start blogging with Hexo!

これだけでセットアップは完了です。作成されたディレクトリに移動すると見慣れたファイルがインストールされています。

1
2
3
$ cd business_blog/
$ ls
_config.yml node_modules package.json scaffolds source themes yarn.lock

ここでnpm installします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ npm install
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN rm not removing /Users/user/github/business-book-reviews/business_blog/node_modules/.bin/stylus as it wasn't installed by /Users/user/github/business-book-reviews/business_blog/node_modules/stylus

> ejs@2.7.4 postinstall /Users/user/github/business-book-reviews/business_blog/node_modules/ejs
> node ./postinstall.js

Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)

npm notice created a lockfile as package-lock.json. You should commit this file.
added 25 packages from 48 contributors, removed 11 packages, updated 166 packages and audited 191 packages in 10.617s

14 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

これで必要なパッケージはインストールできました。

まとめと次回予告

このブログを開始したのは1年半前でした(時が経つのは早いものです。時間は大事にしたいですね)。そのときのhexoのバージョンは3.9でした。今回は5.2を新しくインストールするにあたって3.9のもメンテナンスしたいと思います。

次回は、セットアップしたhexoのconfig周りの設定と、nginxのバーチャルホストの設定をしていきたいと思います。

Google AdSenseを開始しました!

審査結果が届く

前回、Google AdSenseの審査のための準備という記事を書きました。申請をしたのは、この記事を書いた1日前(11月21日の深夜)です。

その後、Google AdSense(以下、AdSense)のホームページに

「審査には数日かかります、場合によっては2週間ほどかかります」

のようなメッセージが表示されていました。結果が出るのをリロードしながら待っていました。

審査の結果を心待ちにしていたら、AdSenseからメールが届きました!

お客様のサイトでAdSense広告を配信する準備が整いました

まず思ったのは審査が通って本当によかったということです!審査に通らず再審査になると、どんどん審査に通りづらくなるという話も聞いたことがあったので、1発で通ってよかったです!

そして審査結果が出るのも早かったです。申請してから1日半くらいで審査結果が出ました。

ads.txtの設定をする

AdSenseの設定をするのははじめてなので、まずメニューを見てみます。

ホーム、広告設定は広告などのメニューがあるのを確認していると、ホームを表示した時にいかにもアラートっぽいメッセージが表示されます。

ads.txtのアラート

なんだろうと思って今すぐ修正のリンクをクリックするとads.txtを作成してねというメッセージが表示されました。そのメッセージの下の方にダウンロードのリンクがあるので、自分用のads.txtをダウンロードします。

ads.txtを配置する

ads.txtはドキュメントルート直下に置くファイルなので、hexoのpublicディレクトリにおきます。

以下のURLで表示確認を行い完了です。

https://book-reviews.blog/ads.txt

アラートはなかなか消えない

設置してみたもののアラートは消えません…心配になりますが、ちゃんと設置できています。

ヘルプを見てみると以下の注意書きがありました。

注: 変更が AdSense に反映されるまでに数日かかる場合があります。サイトの広告リクエストが多くない場合は、最長で 1 か月ほどかかることがあります。

ん〜まぁまぁ時間がかかるみたいですね。PVが少ない=広告リクエストの数は多くないのでアラートが消えるのは気長に待つことにします。

広告を設置する

AdSenseが利用できるようになったのであれば早速広告を設置してみます!

広告掲載の自動化

メニューの広告をクリックすると、広告掲載の自動化というメッセージがまず目に飛び込んできます。仕事でGoogle AdManagerを使って広告の設置を行ったりしていたので、思ったのと違うな〜と思いながら、自動広告をONにしてみました。

編集アイコンをクリックすると広告設定のプレビューで実際にどのように広告が表示されるのかを確認することができます。確認してみると記事上に画面幅と同じ幅の大きな広告が表示されていました。こういう風にしか設定できないのかと思って一旦その設定を適用しました。

広告ユニットを作成する

広告設定が自動のみなわけがなく、やはり設定する箇所がありました。

メニューで広告を選んだ状態で上の方にスクロールすると、広告ユニットごとというタブがありました。それをクリックすると、新しい広告ユニットの作成というメニューが出てきました。

推奨されているディスプレイ広告を作成します。広告ユニットの名前を入力のところに適切な名前をいれて、形をスクエア・横長タイプ・縦長タイプから選んで作成ボタンをクリックします。(広告サイズはレスポンシブのままにしておきます)

するとコードが表示されます。
一番上の<script async src=>のコードは審査時に貼っているので、その下のHTMLを表示したい箇所に貼ります。(hexoでsidebarに広告を貼る方法はまた別の機会にご紹介します)

実際に表示された広告を見てみると、スクエアを選んだはずが縦長の広告が表示されてしまっています…

スクエアが縦長に

原因はわかりませんが、他にも気になる点があるので調整していきます。

marginの調整

記事のmarginの調整

広告の高さは記事とあっていると見た目がよくみえます(個人的に)。なので高さを合わせます。

まず記事の方の更新日時の上が思ったより空いているので、調整します。

記事の上のマージンはarticleというclassで当てられています。
hexoでの定義箇所を探すとthemes/landscape/source/css/_partial/article.stylにありました

1
2
3
1 .article
2 margin: block-margin 0
3

marginはblock-marginという変数を利用していました。この変数に値をいれているところを探します。するとthemes/landscape/source/css/_variables.stylというファイルで指定されていることがわかりました。

1
2
3
4
43 // Layout
44 block-margin = 50px
45 article-padding = 20px
46 mobile-nav-width = 280px

こちらの数字を30pxにしてみます。hexo generateで適用します。

広告のmarginの調整

先ほど追加した広告にmargin-top: 60pxを適用します

1
2
3
4
5
6
7
8
9
<ins class="adsbygoogle"
style="display:block;margin-top: 60px;"
data-ad-client="ca-pub-1078092739514795"
data-ad-slot="9239237218"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>

調整後の確認

ページをリロードして確認してみます。

margin調整後

いい感じになりました!

まとめ

AdSenseの審査が通過したので、必要な設定(ads.txtの設置)を行い、広告タグを作成しました。広告を表示するにあたりレイアウトが気になったのでそちらを調整しました。

このまましばらく様子をみて、必要であればオーバーレイ広告を画面下に表示してみたいと思います。

Google AdSenseの審査のための準備

背景

ブログの収益化のためになんとか今年中にGoogle AdSense(以下、AdSense)の審査を通過したいと思っていました。

ただ、思ったように記事も書けずなかなか進みませんでしたが、今年もあと1ヶ月近くになってしまったので、最低限の準備だけして申請してみることにしました。

AdSenseの審査を受けるための最低限の準備

今回AdSenseの審査を受けるために行ったことは二つです。

  • プライバシーポリシーページの追加
  • ブログ運営者についてのページの追加

ちょっと面倒だなと思っていたのですが、比較的スムーズに追加ができまた。ではそれぞれどのように準備したのかをご紹介したいと思います。

プライバシーポリシーページの準備

プライバシーポリシーページについてはこちらの本で必須と紹介されていたので追加しました。

どこかにプライバシーポリシーの書き方について載っていないかな?とググってみるとやはりありました。本当にありがたかったです。

【ブログに必須!】プライバシーポリシーと免責事項の書き方

こちらのページは、説明もしっかりしていて、サンプルも載っているのでとても便利でした。このページを参考に作成したのが以下のページです。

プライバシーポリシー

ブログ運営者についてページの準備

このページが必須かどうかはわかりませんが、どこかで必須と聞いたことがあったので作成しました。

書く内容については、ちょっと迷いましたが、プロフィールと略歴を記載しました。あと、プライバシーポリシーページに自分の連絡先としてtwitterアカウントを記載したので、それも追加しました。

このブログの運営者について

まとめ

今回、審査を受けるにあたって準備したのは上記の2点のみです。ですが、もちろん有用なコンテンツも必要です。約1年くらいかかりましたが、今までブログを続けてきてよかったと思います。少しではありますが見てくれる方もいらっしゃるのでこれからもがんばって続けていきたいと思います。

AWS Lambdaを実装するときは最初にeventを確認すべき

前回までのまとめ

前回、bqコマンドを使ってデータセットを管理できることを確認しました。今回はcloud functionsを使ってエンドポイントを実装する予定でしたが、いろいろあってAWSを利用することになりました。その中でKinesis Data FirehoseからS3にデータをPUTする前にAWS Lambdaでデータを整形する必要が出てきました。その時に気づいた(当たり前かもしれない)ことを記載します。

関数の作成

まずはじめに関数を作成します。

AWSコンソール画面からAWS Lambdaを選択して、関数の作成をクリックします。

設計図の使用を選び、設計図の検索フォームにはFirehoseと入力して検索します。すると、kinesis-firehose-process-recordという設計図が出てくるので、こちらを選択します。

関数名に適切な名前を入力し、画面右下にある関数の作成ボタンをクリックします。これでLambda関数が作成できました。

コードの確認

設計図から作成されたコードは以下のようになっていました。

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('Loading function');

exports.handler = async (event, context) => {
/* Process the list of records and transform them */
const output = event.records.map((record) => ({
/* This transformation is the "identity" transformation, the data is left intact */
recordId: record.recordId,
result: 'Ok',
data: record.data,
}));
console.log(`Processing completed. Successful records ${output.length}.`);
return { records: output };
};

event.recordsでストリームデータにアクセスできるようです。map内のコールバック関数で各データにアクセスができます。今回はrecord.dataを整形して返せばよさそうです。

データ構造がわからずハマる

実装を始めようと思い、record.dataをいろいろいじって見たのですが、どうにもうまくいきません。そもそもどういうデータなのかわかっていなかったのがよくなかったです…

いろいろ調べていると以下のページが見つかりました。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-kinesis-example.html

このサンプルに

1
//console.log(JSON.stringify(event, null, 2));

と書かれていて、嗚呼、最初にこれを実行すればよかったんだと気づきました。

eventを確認して実装する

eventは以下のようになっていました

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"invocationId": "354ca9e9-7e83-4b8a-8415-7efddfd276a1",
"deliveryStreamArn": "arn:aws:firehose:ap-northeast-1:000000000:deliverystream/sample-stream",
"region": "ap-northeast-1",
"records": [
{
"recordId": "49612424225054340516134952530760216311574238310848004098000000",
"approximateArrivalTimestamp": 1605063330351,
"data": "eyJjb250YWluZXJfaWQiOiJhYmYzYjJkZGQyNWJlYWQzMjIwZjU1MTliYTM0MzZlZjhmNDNhN2QwYzg1MTdjZDhmNGViN2M2NDIyZjY4NzVmIiwiY29udGFpbmVyX25hbWUiOiIvZWNzLWFkZmFpci1hbmFseXRpY3MtZmFyZ2F0ZS10YXNrLTEzLWFkZmFpci1hbmFseXRpY3MtYXAtZGNiMWRiOWY4OWEyZTBlOGQzMDEiLCJlY3NfY2x1c3RlciI6ImFybjphd3M6ZWNzOmFwLW5vcnRoZWFzdC0xOjQ5MzkwNTA5Njc0NTpjbHVzdGVyL2FkZmFpci1hbmFseXRpY3MtZmFyZ2F0ZS1jbHVzdGVyIiwiZWNzX3Rhc2tfYXJuIjoiYXJuOmF3czplY3M6YXAtbm9ydGhlYXN0LTE6NDkzOTA1MDk2NzQ1OnRhc2svYWRmYWlyLWFuYWx5dGljcy1mYXJnYXRlLWNsdXN0ZXIvZDVjM2U3Yzg0ODNlNDQwNDk4ZjhjYjVhZTQ2MmUyNTYiLCJlY3NfdGFza19kZWZpbml0aW9uIjoiYWRmYWlyLWFuYWx5dGljcy1mYXJnYXRlLXRhc2s6MTMiLCJsb2ciOiJ7XCJhXCI6XCJiXCIsXCJjXCI6XCJkXCJ9Iiwic291cmNlIjoic3Rkb3V0In0K"
},
{
"recordId": "49612424225054340516134952530761425237393852940022710274000000",
"approximateArrivalTimestamp": 1605063330354,
"data": "eyJjb250YWluZXJfaWQiOiJhYmYzYjJkZGQyNWJlYWQzMjIwZjU1MTliYTM0MzZlZjhmNDNhN2QwYzg1MTdjZDhmNGViN2M2NDIyZjY4NzVmIiwiY29udGFpbmVyX25hbWUiOiIvZWNzLWFkZmFpci1hbmFseXRpY3MtZmFyZ2F0ZS10YXNrLTEzLWFkZmFpci1hbmFseXRpY3MtYXAtZGNiMWRiOWY4OWEyZTBlOGQzMDEiLCJlY3NfY2x1c3RlciI6ImFybjphd3M6ZWNzOmFwLW5vcnRoZWFzdC0xOjQ5MzkwNTA5Njc0NTpjbHVzdGVyL2FkZmFpci1hbmFseXRpY3MtZmFyZ2F0ZS1jbHVzdGVyIiwiZWNzX3Rhc2tfYXJuIjoiYXJuOmF3czplY3M6YXAtbm9ydGhlYXN0LTE6NDkzOTA1MDk2NzQ1OnRhc2svYWRmYWlyLWFuYWx5dGljcy1mYXJnYXRlLWNsdXN0ZXIvZDVjM2U3Yzg0ODNlNDQwNDk4ZjhjYjVhZTQ2MmUyNTYiLCJlY3NfdGFza19kZWZpbml0aW9uIjoiYWRmYWlyLWFuYWx5dGljcy1mYXJnYXRlLXRhc2s6MTMiLCJsb2ciOiJ7XCJhXCI6XCJiXCIsXCJjXCI6XCJkXCJ9Iiwic291cmNlIjoic3Rkb3V0In0K"
},
{
"recordId": "49612424225054340516134952530762634163213467569197416450000000",
"approximateArrivalTimestamp": 1605063330354,
"data": "eyJjb250YWluZXJfaWQiOiJhYmYzYjJkZGQyNWJlYWQzMjIwZjU1MTliYTM0MzZlZjhmNDNhN2QwYzg1MTdjZDhmNGViN2M2NDIyZjY4NzVmIiwiY29udGFpbmVyX25hbWUiOiIvZWNzLWFkZmFpci1hbmFseXRpY3MtZmFyZ2F0ZS10YXNrLTEzLWFkZmFpci1hbmFseXRpY3MtYXAtZGNiMWRiOWY4OWEyZTBlOGQzMDEiLCJlY3NfY2x1c3RlciI6ImFybjphd3M6ZWNzOmFwLW5vcnRoZWFzdC0xOjQ5MzkwNTA5Njc0NTpjbHVzdGVyL2FkZmFpci1hbmFseXRpY3MtZmFyZ2F0ZS1jbHVzdGVyIiwiZWNzX3Rhc2tfYXJuIjoiYXJuOmF3czplY3M6YXAtbm9ydGhlYXN0LTE6NDkzOTA1MDk2NzQ1OnRhc2svYWRmYWlyLWFuYWx5dGljcy1mYXJnYXRlLWNsdXN0ZXIvZDVjM2U3Yzg0ODNlNDQwNDk4ZjhjYjVhZTQ2MmUyNTYiLCJlY3NfdGFza19kZWZpbml0aW9uIjoiYWRmYWlyLWFuYWx5dGljcy1mYXJnYXRlLXRhc2s6MTMiLCJsb2ciOiJ7XCJhXCI6XCJiXCIsXCJjXCI6XCJkXCJ9Iiwic291cmNlIjoic3Rkb3V0In0K"
}
]
}

record.dataはbase64エンコードされたデータということがわかりました。これでやっと実装ができそうです。

まとめ

Lambdaを実装するときはまずeventの中身を確認するようにしましょう。

次回はAWS Lambdaのデプロイについて記載しようと思います。

bqコマンドでデータセットを管理する

前回までのまとめ

前回、bqコマンドが使えるようセットアップを行いました。今回はbqコマンドを使ってデータセット・スキーマを作成していきたいと思います。

データセットとは

データセットとはなんでしょうか。こちらに書かれていました。RDBでいうデータベースのようなものと思っておけばよさそうです。

データセットの操作

データセットの作成

こちらにあるように、bq mkコマンドを利用します。

1
2
$ bq mk test
Dataset 'project_id:test' successfully created.

無事作成できました。Webコンソールでも確認できます。

データセットの削除

削除にはbq rmコマンドを利用します。

1
2
$ bq rm test
rm: remove dataset 'project_id:test'? (y/N) y

yを入力することで削除できます。確認しなくてもよい場合は-fオプションを指定します。

1
2
$ bq rm -f test
$

メッセージは特に表示されませんが削除されています。

テーブルが存在する場合は-rオプションを指定することで、テーブルごと削除できます。
テーブルを作成するため、スキーマを記載したJSONファイルを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat t1.json
[
{
"name": "id",
"type": "INT64",
"mode": "REQUIRED"
},
{
"name": "name",
"type": "STRING",
"mode": "REQUIRED"
}
]

JSONファイルからテーブルを作成してみます。

1
2
3
4
5
$ bq mk --table project_id:test.t1 ./t1.json
Table 'project_id:test.t1' successfully created.
$
$ bq show --schema test.t1
[{"name":"id","type":"INTEGER","mode":"REQUIRED"},{"name":"name","type":"STRING","mode":"REQUIRED"}]

無事作成できました。

では、テーブルが存在するデータセットを削除してみます。

1
2
3
$ bq rm test
rm: remove dataset 'project_id:test'? (y/N) y
BigQuery error in rm operation: Dataset project_id:test is still in use

テーブルがあると削除できないようです。-rオプションを指定してみます。

1
2
3
$ bq rm -r test
rm: remove dataset 'project_id:test'? (y/N) y
$

削除できました。本来であれば、テーブルを削除後、データセットを削除すべきです。テーブルの削除はテーブル名を指定して行います。

1
2
$ bq rm test.t1
rm: remove table 'project_id:test.t1'? (y/N) y

テーブルを削除すればデータセットも削除できます。

1
2
3
$ bq rm test
rm: remove dataset 'project_id:test'? (y/N) y
$

テーブルの操作

データセットのところで、テーブルについてもいくつか記載しました。ここでまとめます。

JSONファイルによるテーブルの作成

テーブルの作成を行うにはJSONファイルでスキーマを定義してbqコマンドで作成するのが一番楽そうです。

1
2
3
4
$ bq mk test
Dataset 'project_id:test' successfully created.
$ bq mk --table test.t1 t1.json
Table 'project_id:test.t1' successfully created.

スキーマの確認

bq showでスキーマなど確認できます。

1
2
3
4
5
6
7
$ bq show test.t1
Table project_id:test.t1

Last modified Schema Total Rows Total Bytes Expiration Time Partitioning Clustered Fields Labels
----------------- ---------------------------- ------------ ------------- ------------ ------------------- ------------------ --------
28 Oct 21:49:31 |- id: integer (required) 0 0
|- name: string (required)

スキーマのみ確認したい場合は--schemaオプションを指定します。

1
2
$ bq show --schema test.t1
[{"name":"id","type":"INTEGER","mode":"REQUIRED"},{"name":"name","type":"STRING","mode":"REQUIRED"}]

既存のスキーマから新しいスキーマを作るときに参考にできそうですね。

まとめ

前回インストールしたbqコマンドでデータセット、テーブルの基本的さ操作を行なってみました。次はCloud Functionsに挑戦してみようと思います。

bqコマンドをインストールする

背景

仕事でBigQueryを利用することになりました。今までのBigQueryとの接点は、GoogleAnalyticsのデータに対してクエリを実行するくらいでした。
色々わかっていないところがあるので、これを機にしっかりと理解しようと思い、オライリーのGoogle BigQueryを読もうと思いました(5年前の本ですが、全体をざっとみて知りたい内容が多かったので購入しました)

読み進めていると、bqコマンドを利用する箇所がいくつか出てきました。最初は無視して読み進めていたのですが、実際に手を動かしてデータを扱う箇所でもbqコマンドを利用していました。これは環境を準備するしかないと思い、bqコマンドのインストールを行いました。その時のメモです。

全体の流れ

インストールの全体の流れはちゃんとドキュメントが用意されています。

bqコマンドラインツールの利用

このページにある

Cloud SDKをインストールして初期化しますのところをやっていきます。

Google Cloud SDK のインストール

先ほどのリンクにある手順通り行っていきます。

Pythonのインストール

まずはPythonをインストールします。最新のバージョンをインストールすればよいかと思いきや、サポートされているバージョンは 3.5~3.7、2.7.9 以降です。ということなので、3.7の最新版をインストールしようと思います。

まずは、pyenvを最新にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ pyenv
pyenv 1.2.19-6-gbdfed51d
(省略)
$ anyenv update pyenv
Updating 'pyenv'...
| From https://github.com/yyuu/pyenv
| bdfed51d..806b30d6 master -> origin/master
| * [new tag] v1.2.21 -> v1.2.21
| * [new tag] v1.2.20 -> v1.2.20
Skipping 'pyenv/python-build'; not git repo
Updating 'anyenv manifest directory'...
| From https://github.com/anyenv/anyenv-install
| dcbcfe1..d9791df master -> origin/master
$ pyenv
pyenv 1.2.21
(省略)

次に、インストールするバージョンを確認します。

1
2
3
4
5
6
$ pyenv install -l
(省略)
3.7.8
3.7.9
3.8.0
(省略)

3.7系の最新は3.7.9なので3.7.9をインストールします。

1
2
3
4
5
6
7
8
9
10
11
12
$ pyenv install 3.7.9
Downloading openssl-1.1.0j.tar.gz...
-> https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz
Installing openssl-1.1.0j...
Installed openssl-1.1.0j to /Users/user/.anyenv/envs/pyenv/versions/3.7.9

python-build: use readline from homebrew
Downloading Python-3.7.9.tar.xz...
-> https://www.python.org/ftp/python/3.7.9/Python-3.7.9.tar.xz
Installing Python-3.7.9...
python-build: use readline from homebrew
Installed Python-3.7.9 to /Users/user/.anyenv/envs/pyenv/versions/3.7.9

3.7.9を利用するように設定します。

1
2
3
$ pyenv local 3.7.9
$ python --version
Python 3.7.9

Pythonのインストールは完了しました。

SDKのダウンロード

SDKをダウンロードします。
64ビット版をダウンロードします。

アーカイブをファイル システム上の任意の場所に展開します。ホーム ディレクトリを使用することをおすすめします。とのことなので、ホームディレクトリ下に展開します。

1
2
3
4
5
6
7
8
9
$ tar xzf ~/Downloads/google-cloud-sdk-312.0.0-darwin-x86_64.tar.gz -C .
$ cd google-cloud-sdk
$ ls
LICENSE completion.zsh.inc path.bash.inc
README data path.fish.inc
RELEASE_NOTES deb path.zsh.inc
VERSION install.bat platform
bin install.sh properties
completion.bash.inc lib rpm

インストールスクリプトの実行

展開したファイルの中のinstall.shを実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
$ cd ..
$ ./google-cloud-sdk/install.sh
Welcome to the Google Cloud SDK!

To help improve the quality of this product, we collect anonymized usage data
and anonymized stacktraces when crashes are encountered; additional information
is available at <https://cloud.google.com/sdk/usage-statistics>. This data is
handled in accordance with our privacy policy
<https://policies.google.com/privacy>. You may choose to opt in this
collection now (by choosing 'Y' at the below prompt), or at any time in the
future by running the following command:

gcloud config set disable_usage_reporting false

Do you want to help improve the Google Cloud SDK (y/N)? y


Your current Cloud SDK version is: 312.0.0
The latest available version is: 315.0.0

┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Components │
├──────────────────┬──────────────────────────────────────────────────────┬──────────────────────────┬──────────┤
│ Status │ Name │ ID │ Size │
├──────────────────┼──────────────────────────────────────────────────────┼──────────────────────────┼──────────┤
│ Update Available │ BigQuery Command Line Tool │ bq │ < 1 MiB │
│ Update Available │ Cloud SDK Core Libraries │ core │ 15.4 MiB │
│ Update Available │ Cloud Storage Command Line Tool │ gsutil │ 3.5 MiB │
│ Not Installed │ App Engine Go Extensions │ app-engine-go │ 4.8 MiB │
│ Not Installed │ Appctl │ appctl │ 18.5 MiB │
│ Not Installed │ Cloud Bigtable Command Line Tool │ cbt │ 7.6 MiB │
│ Not Installed │ Cloud Bigtable Emulator │ bigtable │ 6.6 MiB │
│ Not Installed │ Cloud Datalab Command Line Tool │ datalab │ < 1 MiB │
│ Not Installed │ Cloud Datastore Emulator │ cloud-datastore-emulator │ 18.4 MiB │
│ Not Installed │ Cloud Firestore Emulator │ cloud-firestore-emulator │ 42.1 MiB │
│ Not Installed │ Cloud Pub/Sub Emulator │ pubsub-emulator │ 56.3 MiB │
│ Not Installed │ Cloud SQL Proxy │ cloud_sql_proxy │ 7.3 MiB │
│ Not Installed │ Emulator Reverse Proxy │ emulator-reverse-proxy │ 14.5 MiB │
│ Not Installed │ Google Cloud Build Local Builder │ cloud-build-local │ 6.2 MiB │
│ Not Installed │ Google Container Registry's Docker credential helper │ docker-credential-gcr │ 1.8 MiB │
│ Not Installed │ Kind │ kind │ 4.4 MiB │
│ Not Installed │ Kustomize │ kustomize │ 22.8 MiB │
│ Not Installed │ Minikube │ minikube │ 24.0 MiB │
│ Not Installed │ Nomos CLI │ nomos │ 17.6 MiB │
│ Not Installed │ Skaffold │ skaffold │ 15.3 MiB │
│ Not Installed │ anthos-auth │ anthos-auth │ 16.2 MiB │
│ Not Installed │ gcloud Alpha Commands │ alpha │ < 1 MiB │
│ Not Installed │ gcloud Beta Commands │ beta │ < 1 MiB │
│ Not Installed │ gcloud app Java Extensions │ app-engine-java │ 59.5 MiB │
│ Not Installed │ gcloud app PHP Extensions │ app-engine-php │ 21.9 MiB │
│ Not Installed │ gcloud app Python Extensions │ app-engine-python │ 6.1 MiB │
│ Not Installed │ gcloud app Python Extensions (Extra Libraries) │ app-engine-python-extras │ 27.1 MiB │
│ Not Installed │ kpt │ kpt │ 11.7 MiB │
│ Not Installed │ kubectl │ kubectl │ < 1 MiB │
│ Not Installed │ pkg │ pkg │ │
└──────────────────┴──────────────────────────────────────────────────────┴──────────────────────────┴──────────┘
To install or remove components at your current SDK version [312.0.0], run:
$ gcloud components install COMPONENT_ID
$ gcloud components remove COMPONENT_ID

To update your SDK installation to the latest version [315.0.0], run:
$ gcloud components update


Modify profile to update your $PATH and enable shell command
completion?

Do you want to continue (Y/n)? Y

The Google Cloud SDK installer will now prompt you to update an rc
file to bring the Google Cloud CLIs into your environment.

Enter a path to an rc file to update, or leave blank to use
[/Users/user/.bash_profile]:
Backing up [/Users/user/.bash_profile] to [/Users/user/.bash_profile.backup].
[/Users/user/.bash_profile] has been updated.

==> Start a new shell for the changes to take effect.


For more information on how to get started, please visit:
https://cloud.google.com/sdk/docs/quickstarts

最新版のSDKに更新

インストールスクリプト実行中の出力に

1
2
To update your SDK installation to the latest version [315.0.0], run:
$ gcloud components update

とあったので、最新版のSDKに更新します。

.bash_profileに書かれた環境変数を読み込むためにシェルを再起動します。

1
$ exec $SHELL -l

上記に書いてあるように最新のSDKに更新します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ gcloud components update


Your current Cloud SDK version is: 312.0.0
You will be upgraded to version: 315.0.0

┌─────────────────────────────────────────────────────────┐
│ These components will be updated. │
├─────────────────────────────────┬────────────┬──────────┤
│ Name │ Version │ Size │
├─────────────────────────────────┼────────────┼──────────┤
│ BigQuery Command Line Tool │ 2.0.62 │ < 1 MiB │
│ Cloud SDK Core Libraries │ 2020.10.16 │ 15.4 MiB │
│ Cloud Storage Command Line Tool │ 4.53 │ 3.5 MiB │
│ gcloud cli dependencies │ 2020.10.02 │ 10.6 MiB │
└─────────────────────────────────┴────────────┴──────────┘

The following release notes are new in this upgrade.

(省略)

Update done!

To revert your SDK to the previously installed version, you may run:
$ gcloud components update --version 312.0.0

これで更新が完了しました。

gcloud initの実行

gcloud initを実行してSDKを初期貸します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ gcloud init
Welcome! This command will take you through the configuration of gcloud.

Your current configuration has been set to: [default]

You can skip diagnostics next time by using the following flag:
gcloud init --skip-diagnostics

Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.
Reachability Check passed.
Network diagnostic passed (1/1 checks passed).

You must log in to continue. Would you like to log in (Y/n)? Y

ここで、Yを入力すると、アカウント確認画面が表示されます。アカウントを選択すると、ブラウザにOAuthの認可画面が表示されます。許可をクリックします。すると、 Google Cloud SDK 認証の完了と表示されます。

ターミナルに戻るとプロジェクトを選択するようになっているので、これから利用するプロジェクトを選択します。数字を入力しエンターを押すと以下のようなメッセージが表示されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Not setting default zone/region (this feature makes it easier to use
[gcloud compute] by setting an appropriate default value for the
--zone and --region flag).
See https://cloud.google.com/compute/docs/gcloud-compute section on how to set
default compute region and zone manually. If you would like [gcloud init] to be
able to do this for you the next time you run it, make sure the
Compute Engine API is enabled for your project on the
https://console.developers.google.com/apis page.

Created a default .boto configuration file at [/Users/user/.boto]. See this file and
[https://cloud.google.com/storage/docs/gsutil/commands/config] for more
information about configuring Google Cloud Storage.
Your Google Cloud SDK is configured and ready to use!

* Commands that require authentication will use shibagaki.motoaki.rt@mynavi.jp by default
* Commands will reference project `mynavi-cm-adfair` by default
Run `gcloud help config` to learn how to change individual settings

This gcloud configuration is called [default]. You can create additional configurations if you work with multiple accounts and/or projects.
Run `gcloud topic configurations` to learn more.

Some things to try next:

* Run `gcloud --help` to see the Cloud Platform services you can interact with. And run `gcloud help COMMAND` to get help on any gcloud command.
* Run `gcloud topic --help` to learn about advanced features of the SDK like arg files and output formatting

デフォルトのzone/regionの設定がされていないと言われますが、 Your Google Cloud SDK is configured and ready to use!とも書かれているので、こちらで使えるかどうか試してみます。

bqコマンドの確認

bqコマンドがあるかどうか確認します

1
2
$ which bq
/Users/user/google-cloud-sdk/bin/bq

パスは通っているようです

1
2
$ bq version
This is BigQuery CLI 2.0.62

大丈夫そうですね。

まとめ

Google BigQueryを読み進めていくためにbqコマンドを利用できるようにしました。読み進めていきながら実際にコマンドを実行したいと思います。

JavaScriptでsmooth scrollを実装する(IE対応)

背景

JavaScriptでsmooth scrollを実装してほしいという依頼を受けました。そのサイトはあるASPを利用していて、なにかをインストールするということはできません。

方法を探す

JavaScript初心者のわたしはとりあえずググることにしました。すると、スムーズスクロールを実装しようとしている人なら誰もが見ていそうなページが見つかりました。

素のJavaScriptで実装したかったので、このページに書かれているwindow.scrollToを使う方法でいこうと思います。

実装

ざっと実装してみました。

1
2
3
4
5
6
7
8
9
10
11
const buffer = 50;
const scrollTarget = document.getElementById('scroll_target');
const scrollTrigger = document.getElementById('scroll_trigger');

scrollTrigger.addEventListener('click', function (e) {
e.preventDefault();
const scrollTargetTop = scrollTarget.getBoundingClientRect().top;
const offsetTop = window.pageYOffset;
const top = scrollTargetTop + offsetTop - buffer;
window.scrollTo({ top: top, behavior: 'smooth' });
});

scroll_triggerというidを付与したaタグにスクロールのイベントリスナーをセットします。そのほか、いくつかのブラウザのAPIを利用しています。

element.getBoundingClientRect

ドキュメント

ドキュメントにあるように、width と height 以外のプロパティは、ビューポートの左上を基準としていますので、ページ内の現在の表示箇所によって返ってくる値は異なります。(ビューポートの左上より下にあればプラスの値、上にあればマイナスの値)

window.pageYOffset

ドキュメント

こちらもドキュメントにあるように、現在のビューポートがページの一番上からどれくらいスクロールしているかを表します。pageYOffsetscrollYのエイリアスだそうです。

そして、今この文章を書いていて気付いたのですが、

1
const top = scrollTargetTop + offsetTop - buffer;

は常に同じ値を返すので(当たり前ですか?w)、addEventListenerの中で毎回計算する必要はありませんでした…

window.scrollTo

ドキュメント

scrollToはドキュメントの構文にあるように、2種類の引数をとります。

1
2
window.scrollTo(x-coord, y-coord)
window.scrollTo(options)

今回利用しているのは引数にoptionsをとる方です。optionsはこちらで定義されています。今回はtopbehaviorを指定しています。

IEで動作させたい

現状のままでは、IEで動作しませんでした…IEでも動作させたいので、手を入れていきます。

scrollToの引数を変える

先ほど書いた通りwindow.scrollToの引数は2種類あって、IEはオブジェクトではない方であれば、スクロールするということで、やってみました。

1
window.scrollTo(0, top);

すると、スムーズスクロールではなく、その位置に移動する動作になってしまいました。これでは最低限の役目は果たしているもののイマイチです。

polyfillの導入

今回一番勉強になったのがこちらです。最初に紹介したスムーズスクロールを紹介していたページに記載されていましたが、npm installしていたので見過ごしていました。npm installせずとも利用できました。

polyfillとは?

ドキュメント

MDN Web Docsにはなんでもありますね…ドキュメントにあるように、最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコードです

polyfillを利用する

https://polyfill.io/v3/url-builder/で利用したい機能のチェックボックスにチェックを入れます。今回はsmoothscrollにチェックを入れます。すると、画面上部にURLが表示されます。copy url to clipboardをクリックしコピーします。

今回のURLは

https://polyfill.io/v3/polyfill.min.js?features=smoothscroll

です。これを<script>で読み込みます。

1
<script src="https://polyfill.io/v3/polyfill.min.js?features=smoothscroll"></script>

この1行を一番最初に書いたJavaScriptの上に追加します。そしてIEで試してみると、スムーズスクロールできていました。

まとめ

JavaScript初心者のわたしがスムーズスクロールを実装するまでを記しました。JavaScript初心者はまずブラウザAPIを正しく理解することが重要だと思いました。MDN Web Docsのドキュメントをちゃんと読むべきですね。

JavaScriptの言語仕様を学ぶのであれば、JavaScript Primerを読みましょう。この本はとてもわかりやすく書かれていて、プログラミングの用語についてもわかりやすく説明してくれています(プリミティブ、リテラルなど)

IEで動作しない場合はpolyfillを最初に調べましょう。1行足すだけでIE対応ができるかもしれません。

参考図書

JavaScript Primer

RailsでAuthorizationヘッダを用いた認証を実装する

背景

仕事でAPIの実装を行うことになりました。ログイン後に発行するtokenでユーザー認証が必要ということで、どういった方法があるかを調べてみました。Authorizationヘッダを用いることが多いのは知っていたので調べてみることにしました。

Authorizationヘッダについて

Authorizationの仕様についてはしっかりと理解していなかったので改めて調べてみました。

Authorization - HTTP | MDN

このサイトに記載されているように、
Authorization: <type> <credentials>
が構文のようです。以前、 Authorization: <credentials>という実装をしていましたが、こちらは仕様に準拠していないことになります。

typeについて

Typeにはいろんな種類があるようです。個人的によく見るのは Bearerです。OAuth認証で利用したりしました。
先ほどのMDNの中にtypeの一覧へのリンクがありました。
Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry

TypeがBasicだった場合、credentialsをBase64でデコードする必要があったりして、少し手間がかかりそうな印象です。

Bearerを試してみる

まずは簡単にできそうなBearerを試してみます。
API側では、authenticate_or_request_with_http_tokenメソッドを用います。

1
2
3
4
5
6
def authenticate
authenticate_or_request_with_http_token do |token, _options|
@user = User.find_by_token token
head status: 401 if @user.blank?
end
end

クライアント側はcurlを使います

1
2
$ curl -H 'Authorization: Bearer xxx_credentials' https://api_endpoint
HTTP Token: Access denied.

認証が失敗してしまいました…
ブロック内でbinding.pryを呼び出してもスルーされることから、ブロックに到達する前に認証に失敗しているようです。

いろいろ調べてみると、以下のサイトが見つかりました。

週刊Railsウォッチ(20191118前編)ActiveJob引数のログ抑制、RailsガイドProプランお試し、ファイルアップロードのレジュームgemほか|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

BearerだけではなくTokenでも良いようなので、Tokenを試してみます

1
$ curl -H 'Authorization: Token xxx_credentials' https://api_endpoint

これだとよくわからないので、レスポンスヘッダを表示してみます

1
2
3
4
5
6
7
8
9
10
11
$ curl -D - -H 'Authorization: Token xxx_credentials' https://api_endpoint
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
ETag: W/"d396de9e73b218ac6b8f0de7967e82bc"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 0f90b919-e9f6-42ee-a76a-96930fbfdff2
X-Runtime: 0.064172
Transfer-Encoding: chunked

認証成功しました!

ということはTokenではよくてBearerはダメということですね。原因を調査するためにauthenticate_or_request_with_http_tokenを調べてみます。
該当のバージョンでgithubを探すと該当箇所が見つかりました。

rails/http_authentication.rb at 4-2-stable · rails/rails · GitHub

なんと、Bearerでは引っかからないようになっています…
Tokenだと認証可能ですが、Tokenはの定義にはありません…

Basicを試す

こうなったら一般的なBasicを試してみます。credentialsの部分はユーザー名:パスワードになりますが、今回ユーザー名がないので:パスワードとなり、パスワードの部分にcredentialsを指定します。

API側ではauthenticate_or_request_with_http_basicを利用します

1
2
3
4
5
6
def authenticate
authenticate_or_request_with_http_basic do |_user, password|
@user = User.find_by_token password
head status: 401 if @user.blank?
end
end

クライアント側はBase64.encode64(‘:xxx_credentials’)でBase64エンコードした文字列を準備します。(先頭の:を忘れない)

1
2
$ curl -H 'Authorization: Basic base64_encode_credentials' https://api_endpoint 
HTTP/1.1 200 OK

うまく認証されました!

まとめ

認証はセキュリティ的に重要な箇所になるので、慎重に実装しましょう。
AuthorizationヘッダなどHTTPの仕様をしっかり理解してからどの方法を使うかを検討しましょう。

おまけ

今回クライアント側としてcurlを使いました。有用なオプションがいっぱいありますね。以下にまとめたいと思います。

  • -D (–dump-header)

    HTTPレスポンスヘッダを表示します。標準出力に出力するには-を指定します。

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ curl -D - https://www.google.com
    HTTP/2 200
    date: Wed, 16 Sep 2020 17:16:16 GMT
    expires: -1
    cache-control: private, max-age=0
    content-type: text/html; charset=ISO-8859-1
    p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
    server: gws
    x-xss-protection: 0
    (省略)
  • -o

    レスポンスボディの出力先を指定します。不要な場合に/dev/nullを指定したりします。

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $ curl -D - -o /dev/null https://www.google.com
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0HTTP/2 200
    date: Wed, 16 Sep 2020 17:19:47 GMT
    expires: -1
    cache-control: private, max-age=0
    content-type: text/html; charset=ISO-8859-1
    p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
    server: gws
    x-xss-protection: 0
    x-frame-options: SAMEORIGIN
    set-cookie: 1P_JAR=2020-09-16-17; expires=Fri, 16-Oct-2020 17:19:47 GMT; path=/; domain=.google.com; Secure
    set-cookie: NID=204=rwcy-xyJUKkXMHIec1I-ETPuB_hzV-mbxnl5ot0DhU2q2B-I-aRIVHKkHFyKvsqESZqjRXQFxn7YjzjmvTkflbrfzePsc87d7F7GFELqNbXvPvATIo2v6EaeaHzIXrsoAL4LRKd20uniGfFR6dN8pq3gB4O4SPDzjYd7EJvWLaA; expires=Thu, 18-Mar-2021 17:19:47 GMT; path=/; domain=.google.com; HttpOnly
    alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
    accept-ranges: none
    vary: Accept-Encoding

    100 13141 0 13141 0 0 62279 0 --:--:-- --:--:-- --:--:-- 62279
  • -s

    進捗を表示しないようにします。

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ curl -D - -o /dev/null -s https://www.google.com
    HTTP/2 200
    date: Wed, 16 Sep 2020 17:21:07 GMT
    expires: -1
    cache-control: private, max-age=0
    content-type: text/html; charset=ISO-8859-1
    p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
    server: gws
    x-xss-protection: 0
    x-frame-options: SAMEORIGIN
    set-cookie: 1P_JAR=2020-09-16-17; expires=Fri, 16-Oct-2020 17:21:07 GMT; path=/; domain=.google.com; Secure
    set-cookie: NID=204=Mdp8loY0lxxV_Kf-YoGRq3db-HvCsX81ffRzePmxfFeT6gb2A72Ijy8AoG5PHsjGj962rqODoZ0VXn9ZMen5MPFGnUww7qnmuexWFLC0JGlyR07K1iIO3iAub_CFUzdjZogChUYvm1Qo9yxyzlm2pb0A2i44va50JOIBi-8qrDE; expires=Thu, 18-Mar-2021 17:21:07 GMT; path=/; domain=.google.com; HttpOnly
    alt-svc: h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
    accept-ranges: none
    vary: Accept-Encoding

    ヘッダだけ欲しい場合はこれで良いですね。

  • -X

    HTTPメソッドを指定します。指定しない場合はGETになります。

    例:

    1
    2
    3
    4
    5
    6
    7
    $ curl -X POST https://www.google.com
    <!DOCTYPE html>
    <html lang=en>
    <meta charset=utf-8>
    <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
    <title>Error 405 (Method Not Allowed)!!1</title>
    (省略)

    405が返ってきてますね…POSTには対応してないようです

  • -F(–form)

    フォームの値を指定します。key=valueという形式で指定します

    例:

    1
    $ curl -X POST -F 'username=user' -F 'password=password' https://somewhere_login_url

他にもいろいろあるようですが、また何かわかったら追記します。

ECSでRailsのassetsを配信する方法

これまでのまとめ

今までGitHub Actionsを用いてECSデプロイする方法を調査してきました。そして、なんとかECSにデプロイできるところまできました。

今回は、デプロイできるようになったものの、アセットが表示されなかったときの対応方法をまとめたいと思います。

状況の確認

ECSは起動タイプにFargateを選択していました。ですのでコンテナの状況はCloudWatchに流れてくるログからのみ知ることができました。

ログにはアセットが見つからないと言う情報だけしかありませんでした。最初はアセットのプリコンパイルが失敗しているのかと思い、コンテナ作成時にプリコンパイルしたり、ECSで起動時にプリコンパイルしたりしましたが状況は変わらずでした。

起動タイプEC2で試す

やはりこういうときはコンテナにアクセスできた方がデバッグが早いです。面倒でしたが起動タイプEC2で起動し、コンテナにアクセスしました。

アセット類はRailsコンテナ内に存在していました。

原因の調査

この時点ではまだ良くわからなかったので、アプリケーションのURLではなく、アセット単体のURLにアクセスしてみました。

するとnginxが404を返してきます。nginxが404を返してくる、そもそもupstreamのRailsにリクエストをフォワードしていないのでは?と思ってnginxの設定を調べてみると、nginxの設定はnginxとRailsが一つのサーバで動作していた時のままでした。

  • nginxのDocumentRootはRAILS_ROOT/public
  • locationの設定もそのまま
  • アセットはnginxが処理し、それ以外はupstreamのRailsにフォワード

これではアセットが表示されないのも当然です。nginxとRailsを別コンテナで動かすという単一責務の法則に従ったことで、以前の設定が使えなくなっていました。

対応

まず、全てのアクセスをupstreamにフォワードするようnginxの設定を変更します。locationの優先順位がちゃんと理解できていなかったことを思い知らされました。nginxについて再度学びたいと思います(後日記載します)。

nginxの設定を変更すると、エラー表示がnginxからRailsに変わりました。これでリクエストがRailsにフォワードされていることがわかります。

Railsのエラーメッセージを確認すると、ActionController::RoutingErrorとなっていて、アセットファイルへのrouteがないと言っています。

原因を確認すると、静的ファイルの配信設定ができていないようでした

1
2
3
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

config/environments/下のファイルに上記のように記載されていたので、タスク定義ファイルに

1
2
3
"environment": [
{ "name": "RAILS_SERVE_STATIC_FILES", "value": "true" }
],

と設定することで、静的ファイルの配信ができるようになりました。

まとめ

nginxとRailsが同じサーバで動作することに慣れてしまっていたので、原因がわかるまで時間がかかってしまいました。わかってしまえばそんなに大したことではないのですが、わかるまでが大変ですね…

今回のことでRailsやnginxの設定がわかっていないことがよくわかりました。nginxの設定、とくにlocationの設定はちゃんと理解しておくべきだと思うので、これから重点的に学びたいと思います。