Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8892 articles
Browse latest View live

LINEトークをwindows10(NODE.js+google-home-notifier+Firebase)経由でgooglehomeに話させる(2019.12 )

$
0
0

はじめに

windowsPCでLINE投稿内容をgooglehomeを話させてみました。先人達の記載の内容を参考にさせていただきました。ありがとうございます。
node v12.13.1
npm v6.12.1
google-home-notifier v1.2.0
windows 10pro

windowsにNODE.js環境

下記を参考にNODE.js環境を構築して、googlehomenotifierを導入します。
Windows PCを使ってGoogle Homeを喋らせてみた

すべてを[C:\Program Files\nodejs]下でインストール。
作業フォルダ配下にするべきでしたが、やりなおす気にはなれず。。
warningがたくさん出ましたが気にせず、進みました。

googlehomenotifier修正

下記を参考に修正
google-home-notifierで"Error: get key failed from google"とエラーが出る問題の対処法
Raspberry PiからGoogle Homeを喋らせる

cd node_modules/google-home-notifier
package.jsonを修正
"google-tts-api";"0.0.3" ⇒"google-tts-api";"0.0.4"
npm update google-tts-api

cd node_modules/mdns/lib
browser.jsを修正
rst.DNSServiceGetAddrInfo()rst.getaddrinfo({families:[4]})

LINE API設定

下記を参考にLINE developerのLINE APIを設定する。GUIが少し変更されている。
Google Home でLINEのメッセージを”おしゃべり”双方向通信(コミュニケーション)をしてみよう.

Messaging API settings
・use Webhook:enable
・Webhook URL:https://XXXXXXXXXXXXXXXXXX.firebaseio.com/line.json ※後述
・Allow bot to join group chats :Enabled
・QRコードからline-botを自分のline友達に追加

Firebase設定

下記を参考に、Firebaseを設定する。
新着メールを知らせてくれるGoogle Home

開発タブ内のDatabaseのルール設定では選択式で下記を選択。

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

config情報は後述のjsで使用する。

下記をインストールする。
npm install firebase
npm install superagent

コード

Google HomeにLINEのメッセージを読み上げさせる(LINE BOT+Webhook+Firebase+Node.js)
新着メールを知らせてくれるGoogle Home
以上のサイトをほぼ参考にさせていただいております。ありがとうございます。

この他には、
JavaScript でのインストールと設定
firebase. database. Reference
が参考になります。

var request = require('superagent');
var firebase = require("firebase");
var googlehome = require('./node_modules/google-home-notifier');
var language = 'ja';
googlehome.device('Google-Home', language); 

function googlehomespeak(text) {
googlehome.notify(text, function(res) {
  console.log(res);
});

}

  // Set the configuration for your app
  // TODO: Replace with your project's config object
  var config = {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "XXXXXXXXXXXXX.firebaseapp.com",
    databaseURL: "https://XXXXXXXXXXXX.firebaseio.com",
    projectId: "XXXXXXXXXXXXX",
    storageBucket: "XXXXXXXXXXXXX.appspot.com",
    messagingSenderId: "XXXXXXXXXXXXX"
  };
  firebase.initializeApp(config);

  // Get a reference to the database service
  var database = firebase.database();


// Find the two shortest dinosaurs.
// var ref = firebase.database().ref("dinosaurs");
var ref = firebase.database().ref('/line');
ref.on("child_added", function(snapshot) {
  // This will be called exactly two times (unless there are less than two
  // dinosaurs in the Database).

  // It will also get fired again if one of the first two dinosaurs is
  // removed from the data set, as a new dinosaur will now be the second
  // shortest.

  var data = snapshot.val();
  snapshot.ref.remove();
 var text = data.events[0].message.text;

  console.log(snapshot.key);
  googlehomespeak(text);
});


送信テスト

Lineの友達にline-botを追加してグループを作成し、トークしてGoogleHomeが話すか確認する。


AWS+NodeJSでサーバレスな環境構築②

$
0
0

はじめに

前回の記事ではAPI Gateway+Lambda(NodeJS)を組み合わせてWEBページを表示するというアウトプットでした。今回はDynamoDBのテーブルと項目作成、Lambda関数で使うロールやインラインポリシーの設定を載せていきます。サーバレスに関しては個人的に興味があるとのと、次の案件で用いるからその予習になります。自身も初めてということもあり、表現がわかりにくいところもあるかもしれません。その場合は容赦無く、コメントで指摘していただければ幸いです。
※サーバレスに関してよくわからない方は、前回の記事をご覧いただければと思います。

DynamoDBってなぁに?

簡単に言ってしまうと、AWSがマネージドサービスとして提供しているNoSQL(非リレーショナル)データベースになります。「値」とそれを取得するための「キー」だけを格納するというシンプルな機能を持った「Key-Valueストア」です。

一般的なユースケース
・ミリ秒単位のアクセスレイテンシーが求められる
・データの拡張性が求められる

参考記事
NoSQLとは
DynamoDBをわかりやすく説明

DynamoDBテーブルの作成

DynamoDBダッシュボード>テーブルの作成>テーブル名とプライマリキーだけ入力>作成ボタン

スクリーンショット 2020-01-03 13.32.13.png

項目タブを選択>項目の作成>以下のように項目と値を追加

スクリーンショット 2020-01-03 15.19.31.png
スクリーンショット 2020-01-03 15.20.39.png

あとで使うので、DynamoDBテーブルのリソース名をコピー(黒枠部分)しておきます

スクリーンショット 2020-01-03 15.23.02.png

IAMでロールの作成

IAMダッシュボードのロールを選択>ロール作成ボタン

スクリーンショット 2020-01-03 15.40.49.png

Lambdaを選択>次のステップへ

スクリーンショット 2020-01-03 15.41.56.png

AWSLambdaBasicExecutionRoleにチェック>次のステップへ

スクリーンショット 2020-01-03 15.59.25.png

タグの追加 (オプション)そのまま>次のステップへ

スクリーンショット 2020-01-03 16.02.25.png

ロール名の入力>作成ボタン

スクリーンショット 2020-01-03 16.04.15.png

作成したロールでインラインポリシーの作成

ロール一覧から作成したロールを選択>概要画面でインラインポリシーの作成を選択

スクリーンショット 2020-01-03 16.09.06.png
スクリーンショット 2020-01-03 16.09.59.png

サービスをDynamoDBを選択>2つアクションを追加(GetItemとPutItem)

スクリーンショット 2020-01-03 16.22.29.png

リソースのARNを指定>追加

スクリーンショット 2020-01-03 16.27.35.png

ポリシーの確認>ポリシー名>ポリシーの作成

スクリーンショット 2020-01-03 16.30.59.png
スクリーンショット 2020-01-03 16.35.04.png
スクリーンショット 2020-01-03 16.35.59.png

最後に

次回はAPI Gateway(REST API)+Lambda(NodeJS)+DynamoDBの組み合わせで、DynamoDBのテーブルが更新されるようにしていきます。

4年ぶりにエンジニアに戻った人の話

$
0
0

こんにちは、Qiita初投稿です。

色々あって昨年末に4年間勤めていた外資系のゲーム会社でのプロデューサー業を辞めてエンジニアに戻りました。
前職ではエンジニアではなく、翻訳やアカウントマネージメント系の仕事などをやっていました。
エンジニアブログを書く前に、今回は元エンジニアが4年間経ってエンジニアに戻って世界が一変していた、浦島太郎のような話をしたいと思います。

経歴:

2011年: 大手企業に新卒でエンジニア就職

秋入社というのもありまさかの新卒唯一の日本人、まわりは中国人とインド人ばっかり。入社一日目でエンジニア職人気なさすぎオワタと思う。まわりのエンジニアが毎月行われれていた深夜作業に駆り出されていくのを怯える毎日(結局一度もその作業をすることはなかったが)

2013年: ベンチャー企業へ転職

前職ではテスト業務しか触れず、いつの間にかポジションもQAエンジニアになってしまったのでエンジニアらしいことがしたいと一念発起して、当時黎明期だったモバイルアプリ業界へ転職。たまたま入社直後にアプリがヒットし、会社が急成長。カスタマーサポート担当→QAエンジニア→サーバーエンジニアと役割を変え、気づいたらエンジニアリードになっていった。

2015年: 外資系ゲーム会社へ転職

ヘッドハンティングで当時日本サーバーを建てようとしていた世界的人気のゲームを運営する会社へ入社。エンジニアではなく語学力+QA経験でLocalization QAを任される。そのうち、「君これもできるよね?」というノリでユーザーコミュニケーション担当をやっていたら、そちらが本業になりLocalizationチームから外れてMarketingチームへ異動。Abemaでテレビ番組のプロデューサーをやっていたら、半年で今度は当時のesports担当が外れたので急遽、責任者に就任。

2019年: 現在の会社へ

前職を8月末に辞表を出し11月末に退社(理由は色々あったので割愛)。就職先を決めていなかったので、「いい機会だし色んな業界の話を聞いてみよう」と多くの会社に面接に行っていたら現在の社長と意気投合、はれて4年ぶりにエンジニアに復職。

エンジニアに戻ってきて

前置きが長くなってしまいましたが、ここからが本題。まず転職時の感想ですが。

エンジニア職が好待遇に

昔はエンジニア職といえば3K(Kitsui, Kitanai, Kaerenai)のイメージが強かったです。実際「え、エンジニアなんてやってるんですか?」と大学時代の知り合いに言われたぐらいで、エンジニアなんてさっさとやめてマネージメントに入るのが勝ち組なんて影では言われていました。

正直直近の転職活動の時も、最初は「エンジニア戻るのはないなー」と思って、それでも経歴上エージェントから話が来るので渋々話を聞きにいっていたのですが。

…あれ?エンジニア職の待遇変わってね?

聞くと、今IT系エンジニアの求人倍率は10倍なんですね。昔は「私の代わりはいくらでもいるもの」という感じだったのですが、みんなどこへ行ってしまったのか…当然ながら給与も想像より上、同じように応募した翻訳やプロデューサー業よりも上でした。

技術の進歩

エンジニアに4年ぶりに戻るとあって、有給消化中はほぼ毎日10時間ぐらい今の技術を勉強に費やしていました。
完全にキャッチアップとはまだまだ遠いですが、思ったことを簡単に書くと、

サーバーインフラ構築が簡単に

本当にこれ、当時(2013年ぐらい)だとAWSのEC2とS3とCloudFront使ってサーバーエンジニアが汗と涙で毎回デプロイしてたのに、DockerとEBS組み合わせたら何も考えずにサーバーが建てられる、しかもコンテナモデルだからデプロイが速い速い。あとサーバーレスアーキテクチャを使ったマイクロサービスとか、昔だったらEC2の別サーバーでバッチ処理させるしかなかったのに、凄い(求: 語彙力)

Javascriptって便利じゃない?

昔のJavascriptには何度泣かされたことか。しかしいざ、Vue.js触ってみて思ったのが「これ以外の言語で非同期処理って書けるの?」とまで思わされてしまうほど、Javascriptが使える言語になっていたのに驚き。でも言語仕様はやっぱり好きじゃない

Pythonが主流言語に

これが地味に一番嬉しい。大学で時代を先取りしすぎた教授たちに「明日から課題はPythonで提出して」(当時はJavaで提出していた)と言われかれこれ10年以上Pythonを使っているのに、日本でPythonを使うのはスクリプト処理の時ぐらい。サーバーはRuby on Railsだったのも今は昔、若手のエンジニアから「RoR使ってるのは30代後半のおっさん」と言われ軽くショックを受けました。…ええそのせいもあって、弊社はDjangoでサーバー書いてます。

最後に

ここまでつらつらと書きました。エンジニアに戻ってまだ日も浅いのでまだまだわからないことが多いのですが、4年ぶりに戻ってきて思っていることは

  1. エンジニアは雇うから、育てる職種に
    エンジニアの待遇が良くなったという反面、優秀なエンジニアを採用するというのが本当に難しい時代になったと思います。反面、今までのような技術的なハードルは下がってきているので、きっちりと育てることのできるエンジニアをいかに採用し戦力としていくことが重要に思えます。

  2. エンジニアマーケットのグローバル化
    Pythonなどの言語が主流言語として台頭してくる中で、多くのドキュメントなどのリソースは英語で書かれており、海外には多くのコミュニティも存在します。そのため、日本語だけだと最先端の技術を取り込んでいくための情報量に限りがあります。10年ぐらい前からも言われていることですが、エンジニアとして今優先すべきは英語での語学力だと思っています。合わせて、採用も日本人だけでなく海外の技術者を取り込んでいくことが重要なファクターとなると考えています。

2をふまえて、今の会社でのエンジニア採用は「英語のみ可」に入社後に変更し、社内での英語の義務化を導入するか目下検討しています。1は会社のフェーズとして育てられる環境がないのでまだできていませんが、組織が安定してき次第取り掛かりたいと思っています。

ではでは

node.jsでAbemaをダウンロードする

AWS+NodeJSでサーバレスな環境構築③

$
0
0

はじめに

前回の記事ではDynamoDBのテーブルと項目作成、Lambda関数で使うロールやインラインポリシーの設定を行いました。今回はその続きで、API Gateway(REST APIでCRUD実装)をトリガーにし、Lambda(NodeJS)関数呼び出して、DynamoDBに参照や更新をできるようにします。
表現等が不適切の場合はご指摘いただければ、幸いです。
※サーバレスに関してよくわからない方は、こちらをご覧いただければと思います。

アーキテクチャ図

serverless.png

流れ

  • ソースで直接(Api gatewayを使わず)参照や更新ができるかを確認
  • POST(ユーザーの登録)
    • domain.com/users/{id}
  • DELETE(対象ユーザーの削除)
    • domain.com/users/{id}
  • GET(全ユーザーの取得)
    • domain.com/users
  • PATCH(対象ユーザーの更新)
    • domain.com/users/{id}

ソースで直接(Api gatewayを使わず)参照や更新ができるかを確認

まずはLambda(NodeJS)でDynamoDBテーブルが参照・更新できるか確認
Lambdaダッシュボード>関数の作成>一から作成>以下の通り入力>関数の作成ボタン
※既存のロール設定は前回の記事で作成したものを使用しています。
スクリーンショット 2020-01-03 19.43.16.png

データの参照ができるよう、ソース編集
こちらのリファレンスを参考にしました。

index.js
'use strict';constAWS=require('aws-sdk');constmyRegion="us-east-2";AWS.config.update({region:myRegion});exports.handler=async(event,context)=>{constdocumentClient=newAWS.DynamoDB.DocumentClient({region:myRegion});//読込用のデータconstparams1={TableName:"Users",Key:{id:"01"}};//書込用のデータconstparams2={TableName:"Users",Item:{id:"02",firstname:"first02",lastname:"last02"}};try{constreadData=awaitdocumentClient.get(params1).promise();constwriteData=awaitdocumentClient.put(params2).promise();console.log(readData);console.log(writeData);}catch(err){console.log(err);}};

⚠️ 必ず、DynamoDBとLambdaは同じリージョンで作成するように気をつける。また上記ソースのmyRegion変数値も合わせるように気をつける。いずれが満たない場合、AccessDeniedExceptionという意味不明なエラーが発生する可能性があります。

テストイベントの設定(今回はgetUserData)>テストボタン>取得結果が表示されれば、OK
スクリーンショット 2020-01-03 19.58.10.png

DynamoDBにデータ作成されていることを確認
ちなみに、params2でidをそのまま、firstnameやlastnameを変更すると更新される仕様です。
スクリーンショット 2020-01-03 20.00.54.png

API Gatewayトリガー追加と設定

API Gatewayダッシュボード>APIを作成ボタン>REST APIの構築ボタン(Privateじゃない方)
以下のように入力>APIの作成ボタン
スクリーンショット 2020-01-03 20.32.20.png

エッジ最適化について(AWS公式より引用)

エッジ最適化 API は、API ゲートウェイによって作成および管理される CloudFront ディストリビューション経由でアクセスするエンドポイントです。以前は、エッジ最適化 API は、API ゲートウェイを使用して API を作成する際のデフォルトオプションでした。

参考記事
0からREST APIについて調べてみた
エッジ最適化について説明

リクエスト、レスポンスマッピングのモデルを作成及び設定

参考
モデル名、コンテンツタイプ、スキーマ(ソース)を設定します。
スクリーンショット 2020-01-03 21.41.42.png

スキーマのソース
{"$schema":"http://json-schema.org/draft-04/schema#","title":"UsersInputModel","type":"object","properties":{"id":{"type":"string"},"firstname":{"type":"string"},"lastname":{"type":"string"}}}

アクション>リソースの作成
リソースは2つ作成します。
スクリーンショット 2020-01-03 21.13.48.png
スクリーンショット 2020-01-03 21.15.55.png

POSTメソッド実装(ユーザーの登録)

ここではAPI GatewayでPOSTメソッドを作成して、レコードが登録されるかを確認する。

API Gatewayと紐付けるLambda関数を作成と設定

関数名はputUsersで、その他の設定は前回と一緒(察して)(冒頭のLambda関数作成のスクリーンショット参照)

以下のソースを貼り付けます。
DynamoDBの構成に関しては、こちらの記事を参考にしてください。(DynamoDBテーブルの作成部分)

index.js
'use strict';constAWS=require('aws-sdk');constmyRegion="us-east-2";AWS.config.update({region:myRegion});exports.handler=async(event,context)=>{constdocumentClient=newAWS.DynamoDB.DocumentClient({region:myRegion});letresponseBody="";letstatusCode=0;const{id,firstname,lastname}=JSON.parse(event.body);constparams={TableName:"Users",Item:{id:id,firstname:firstname,lastname:lastname}};try{constdata=awaitdocumentClient.put(params).promise();responseBody=JSON.stringify(data);statusCode=201;}catch(err){responseBody=`Unable to put user ${err}`;statusCode=403;}constresponse={statusCode:statusCode,headers:{"Content-Type":"application/json"},body:responseBody};returnresponse;}

POSTメソッド作成と設定

アクション>メソッドの作成
スクリーンショット 2020-01-03 21.24.17.png

メソッドリクエストの設定
モデルの追加には上記の作成したモデルを追加する
スクリーンショット 2020-01-03 21.46.26.png

API Gatewayで確認テスト

下の方にテストボタンあるので、それを押すとテスト結果(ログも含めて)画面右側に表示されると思います。
スクリーンショット 2020-01-03 22.12.05.png

スクリーンショット 2020-01-03 22.23.42.png

リクエスト本文
{"id":"03","firstname":"first03","lastname":"last03"}

テストステータスで201が返ってこれば、成功みたいです。
⚠️ ちなみに本来DynamoDBテーブルにない項目論理名(例えばfirstnameではなく、あえてnickname)をリクエスト本文に設定された場合、問題なくテスト処理は実行されます。対象のレコードfirstnameには空白が入る仕様になっています。

DynamoDBテーブルにレコードが作成されていることを確認
スクリーンショット 2020-01-03 22.27.09.png

to be continued

ちょっと長くなりそうなので、続きを次回出します。
あとはDELETE(対象ユーザーの削除), GET(全ユーザーの取得) PATCH(対象ユーザーの更新)のメソッドを作成して、最後までCRUD機能を実装していきます。

AWS+NodeJSでサーバレスな環境構築④

$
0
0

はじめに

今回は前回の続きで、DELETE(対象ユーザーの削除), GET(全ユーザーの取得) PATCH(対象ユーザーの更新)を作っていきます。

DELETE(対象ユーザーの削除)

指定されたidを元に、対象のユーザーが削除されるようにしていきます。

Lambda関数の作成と設定

スクリーンショット 2020-01-04 15.20.08.png

ソース

index.js
'use strict';constAWS=require('aws-sdk');constmyRegion="us-east-2";AWS.config.update({region:myRegion});exports.handler=async(event,context)=>{constdocumentClient=newAWS.DynamoDB.DocumentClient({region:myRegion});letresponseBody="";letstatusCode=0;const{id}=event.pathParameters;constparams={TableName:"Users",Key:{id:id}};try{constdata=awaitdocumentClient.delete(params).promise();responseBody=JSON.stringify(data);statusCode=204;}catch(err){responseBody=`Unable to delete user: ${err}`;statusCode=403;}constresponse={statusCode:statusCode,headers:{"Content-Type":"application/json"},body:responseBody};returnresponse;};

メソッド作成と設定

スクリーンショット 2020-01-03 23.23.55.png
スクリーンショット 2020-01-03 23.32.59.png

テスト実行と確認

テストボタン
スクリーンショット 2020-01-03 23.42.51.png
削除されていることを確認
スクリーンショット 2020-01-04 15.36.19.png

GET(全ユーザーの取得)

GETメソッドを実行したとき、全ユーザーが取得されるようにする。

Lambda関数の作成と設定

スクリーンショット 2020-01-04 11.47.15.png
ソース

index.js
'use strict';constAWS=require('aws-sdk');constmyRegion="us-east-2";AWS.config.update({region:myRegion});exports.handler=async(event,context)=>{constdocumentClient=newAWS.DynamoDB.DocumentClient({region:myRegion});letresponseBody="";letstatusCode=0;constparams={TableName:"Users"};try{constdata=awaitdocumentClient.scan(params).promise();responseBody=JSON.stringify(data);statusCode=200;}catch(err){responseBody=`Unable to get users: ${err}`;statusCode=403;}constresponse={statusCode:statusCode,headers:{"Content-Type":"application/json"},body:responseBody};returnresponse;};

メソッド作成と設定

スクリーンショット 2020-01-04 11.48.16.png
スクリーンショット 2020-01-04 11.49.15.png

 テスト実行と確認

何も入力せつ、テストボタン。
スクリーンショット 2020-01-04 15.50.36.png

PATCH(対象ユーザーの更新)

PATCHメソッドを実行したとき、対象のユーザが更新されるようにする。

Lambda関数の作成と設定

スクリーンショット 2020-01-04 12.11.31.png

メソッド作成と設定

スクリーンショット 2020-01-04 12.12.22.png
以下のように設定、赤枠使うのが面倒になったので察して
スクリーンショット 2020-01-04 12.14.32.png

 テスト実行と確認

スクリーンショット 2020-01-04 12.20.18.png

リクエスト本文
{"id":"02","firstname":"update_firstname","lastname":"update_lastname"}

更新されることを確認
スクリーンショット 2020-01-04 12.24.17.png

終わりに

これでサーバレスでREST APIを使ったCRUD作成の環境構築をしました。
次はAPI Gateway+Lambda(NodeJS)+S3を組み合わせていこうと思います。
kinesisやauroraあたりを組み合わせてやりたいです。

試しに「OpenID Connect Provider Certification」を通してみた

$
0
0

以前、なんちゃってOpenID Connectサーバを立ち上げました。( なんちゃってOAuth2/OpenID Connectサーバを自作する )

それはそれで役に立っているのですが、OpenID Connectに準拠しているかどうかを確認するためのTest Suite があったので、物は試しで通してみました。
結果は、散々でした。Optional機能は実装していないので当然ではありますが。

Conformance Testing for OPs
 https://openid.net/certification/testing/

テストの準備

まずは、準備として、テスト対象のエンドポイントのURLやサポートする機能を指定します。

 https://op.certification.openid.net:60000/

image.png

New ボタンを押下します。

image.png

Issuerに、サーバで生成するトークンに含めるissの値を指定します。
Response Typeとして「code」を選択しました。

最後に、「Create」ボタンを押下します。

次は、各エンドポイントなどを指定します。

image.png

以下の値を指定することで、テストを開始できました。

・contact_email : 適当な値
・authorization_endpoint : 認証エンドポイント
・jwks_uri : 署名を検証するための公開鍵のエンドポイント
・token_endpoint : トークンエンドポイント
・userinfo_endpoint : USERINFOエンドポイント
・client_id : 適当な値
・client_secret : 適当な値

最後に、「Save & Start」ボタンを押下すると、テスト用のページに進みます。

image.png

いざ、テスト実施

左側の列にある再生ボタンを押下していくと、それぞれテストが実施されます。
結果はこんな感じです。

image.png

まあ、実装を手抜きしているので当然ですね。
右側の列にある「!」マークのボタンを押下すれば、実行経過やエラー理由が確認できます。

エラーとなった理由を列挙しておきます。

(OP-Response-Missing)
・エラーメッセージを返すべきだが、HTTPエラーステータスが返ってきている。Swaggerで必須パラメータを指定しているがそれがないため

(OP-ClientAuth-SecretPost-Static)
(OP-claims-essential)
(OP-nonce-code)
(OP-prompt-none-LotLoggedIn)
(OP-Req-acr_values)
(OP-Req-login_hint)
(OP-Req-max_age=1)
(OP-Req-max_age=100000)
・OpenID Connectの仕様上Optionalな機能であり、対応していません。

(OP-redirect_uri-NotReg)
・リダイレクトURIをチェックしていないため(手抜き実装)

(OP-Oauth-2nd)
(OP-OAuth-2nd-30s)
(OP-OAuth-2nd-Revokes)
・認可コードの期限チェックや利用済みのチェックをしていないため(手抜き実装)

(参考) OpenID Connect Core 1.0 日本語訳
 http://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html

終わりに

なんちゃっての実装でしたが、こうしてやって実施してみると、すんなり確認が通ってよかったよかった。

以上

npm コマンドでグローバルインストールされたパッケージを表示する

$
0
0

次のコマンドを実行することで、グローバルインストールされたパッケージが表示される。

npm ls-g--depth=0
  • npm ls : インストールされているパッケージを表示する
  • npm ls -g : グローバルインストールされているパッケージをする
  • npm ls --depth=0 : トップレベルのパッケージのみを表示する

npm コマンドでグローバルインストールされたパッケージを表示する

$
0
0

次のコマンドを実行することで、グローバルインストールされたパッケージが表示される。

npm ls-g--depth=0
  • npm ls : インストールされているパッケージを表示する
  • npm ls -g : グローバルインストールされているパッケージをする
  • npm ls --depth=0 : トップレベルのパッケージのみを表示する

各種 Web Application Framework メモリ使用量比較調査 (Go, Ruby, Python, Node.js, Java)

$
0
0

概要

  • 「Hello World な HTML を返す程度の Web アプリケーション」または「それぞれの公式ドキュメントに載っているチュートリアルの初期状態に近いもの」を作成してメモリ使用量を比較する
  • Web アプリケーションサーバは現時点でよく使われていると思われるものを使用する
  • テンプレートエンジンはフレームワークのデフォルト設定またはフレームワークが推奨しているものを使用する
  • macOS Catalina 上でメモリ使用量を計測
  • メモリ使用量の計測には ps コマンドの RSS 値を使う (複数のプロセスを使うものはRSS値を単純に足したものをメモリ使用量とした。メモリ共有部分については考慮していないため、もう少しメモリ使用量が少ないかもしれない)

調査結果

メモリ使用量が少ない順に並べる。

  • 7MB: Go 1.13 + Gin 1.5
  • 10MB: Go 1.13 + Revel 0.21
  • 24MB: Ruby 2.7 + Sinatra 2.0 + Puma 4.3
  • 40MB: Python 3.8 + Flask 1.1 + Gunicorn 20.0
  • 42MB: Node.js 12.14 + Express 4.16
  • 51MB: Python 3.8 + Django 3.0 + Gunicorn 20.0
  • 80MB: Ruby 2.7 + Ruby on Rails 6.0 + Puma 4.3
  • 167MB: Java 11.0 + Spring Boot 2.2 + Tomcat Embed Core 9.0

計測環境の構築と計測

Go 1.13 + Gin 1.5

main.go
packagemainimport("net/http""github.com/gin-gonic/gin")funcmain(){router:=gin.Default()router.LoadHTMLGlob("templates/*")router.GET("/hello",func(c*gin.Context){c.HTML(http.StatusOK,"hello.tmpl",gin.H{})})router.Run(":8080")}
templates/hello.tmpl
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
$ go build main.go
$ GIN_MODE=release ./main
$ curl http://localhost:8080/hello
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 30713   0.0  0.1  4405168   7220 s000  S+   12:04AM   0:00.03 ./main

Go 1.13 + Revel 0.21

Creating a new Revel application | Revel - A Web Application Framework for Go!

$ revel new myapp

Revel tool | Revel - A Web Application Framework for Go!

$ revel package -a myapp -m prod

Deployment | Revel - A Web Application Framework for Go!

$ tar zxvf myapp.tar.gz
run.sh
#!/bin/shSCRIPTPATH=$(cd"$(dirname"$0")";pwd)"$SCRIPTPATH/myapp"-importPath myapp -srcPath"$SCRIPTPATH/src"-runMode prod
$ ./run.sh 
Revel engine is listening on.. localhost:9000
$ curl http://localhost:9000/
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 32777   0.0  0.1  4405468   8928 s000  S+   12:32AM   0:00.04 /Users/hoge/go/myapp
hoge 32771   0.0  0.0  4288312   1048 s000  S+   12:32AM   0:00.01 /bin/sh ./run.sh

Ruby 2.7 + Sinatra 2.0 + Puma 4.3

  • Sinatra
  • 構成: Ruby 2.7.0 + Sinatra 2.0.8.1 + Erb + Puma 4.3.1
myapp.rb
require'sinatra'get'/hello'doerb:helloend
hello.erb
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello, World!</title>
</head>
<body>
Hello, World!
</body>
</html>
$ APP_ENV=production ruby myapp.rb
== Sinatra (v2.0.8.1) has taken the stage on 4567 for production with backup from Puma
Puma starting in single mode...
* Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:4567
Use Ctrl-C to stop
$ curl http://localhost:4567/hello
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 20892   0.0  0.3  4377308  24576 s000  S+   10:17PM   0:00.73 puma 4.3.1 (tcp://0.0.0.0:4567) [ruby-sinatra]

Python 3.8 + Flask 1.1 + Gunicorn 20.0

myapp.py
fromflaskimportFlask,render_templateapp=Flask(__name__)@app.route("/hello")defhello():returnrender_template("hello.html")
templates/hello.html
<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Hello, World!</title></head><body>
Hello, World!
</body></html>
$ gunicorn myapp:app
[2020-01-04 23:55:18 +0900] [29978] [INFO] Starting gunicorn 20.0.4
[2020-01-04 23:55:18 +0900] [29978] [INFO] Listening at: http://127.0.0.1:8000 (29978)
[2020-01-04 23:55:18 +0900] [29978] [INFO] Using worker: sync
[2020-01-04 23:55:18 +0900] [29995] [INFO] Booting worker with pid: 29995
$ curl http://localhost:8000/hello
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 29995   0.0  0.3  4273780  22632 s000  S+   11:55PM   0:00.37 /Users/hoge/.pyenv
hoge 29978   0.0  0.2  4265996  18532 s000  S+   11:55PM   0:00.46 /Users/hoge/.pyenv

Node.js 12.14 + Express 4.16

Express application generator

$ express --view=pug myapp
$ cd myapp/

$ npm install

$ npm audit fix
$ node ./bin/www
$ curl http://localhost:3000/
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 19537   0.0  0.5  4565284  43228 s000  S+    9:59PM   0:00.83 node ./bin/www

Python 3.8 + Django 3.0 + Gunicorn 20.0

はじめての Django アプリ作成、その 1 | Django ドキュメント | Django

$ django-admin startproject mysite

$ python manage.py startapp polls

mysite/settings.py の設定値の一部を修正。

DEBUG = False

ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]']

# Application definition

INSTALLED_APPS = [
    'polls',

はじめての Django アプリ作成、その 3 | Django ドキュメント | Django

polls/templates/polls/index.html
<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Hello, World!</title></head><body>
Hello, World!
</body></html>
polls/views.py
fromdjango.shortcutsimportrenderdefindex(request):returnrender(request,'polls/index.html')
$ gunicorn mysite.wsgi:application
[2020-01-04 23:44:32 +0900] [29172] [INFO] Starting gunicorn 20.0.4
[2020-01-04 23:44:32 +0900] [29172] [INFO] Listening at: http://127.0.0.1:8000 (29172)
[2020-01-04 23:44:32 +0900] [29172] [INFO] Using worker: sync
[2020-01-04 23:44:32 +0900] [29191] [INFO] Booting worker with pid: 29191
$ curl http://localhost:8000/polls/
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 29191   0.0  0.4  4283728  34016 s000  S+   11:44PM   0:00.55 /Users/hoge/.pyenv
hoge 29172   0.0  0.2  4265996  18552 s000  S+   11:44PM   0:00.43 /Users/hoge/.pyenv

Ruby 2.7 + Ruby on Rails 6.0+ Puma 4.3

Getting Started with Rails — Ruby on Rails Guides

$ rails new blog
$ cd blog
$ rails generate controller Welcome index
app/views/welcome/index.html.erb
Hello, World
config/routes.rb
Rails.application.routes.drawdoget'welcome/index'root'welcome#index'end
$ RAILS_ENV=production rails assets:precompile
$ rails server -e production
=> Booting Puma
=> Rails 6.0.2.1 application starting in production 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
$ curl http://localhost:3000/
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 66416   0.0  1.0  4445976  81964 s000  S+    9:42AM   0:02.97 puma 4.3.1 (tcp://0.0.0.0:3000) [blog]

Java 11.0 + Spring Boot 2.2 + Tomcat Embed Core 9.0

  • Spring Boot
  • 構成: OpenJDK 11.0.2 + Spring Boot 2.2.2 + Spring Web + Thymeleaf 3.0.11 + Tomcat Embed Core 9.0.29
build.gradle
plugins{id'org.springframework.boot'version'2.2.2.RELEASE'id'io.spring.dependency-management'version'1.0.8.RELEASE'id'java'}group='com.example'version='0.0.1-SNAPSHOT'sourceCompatibility='11'repositories{mavenCentral()}dependencies{implementation'org.springframework.boot:spring-boot-starter-thymeleaf'implementation'org.springframework.boot:spring-boot-starter-web'}
DemoApplication.java
packagecom.example.demo;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.GetMapping;@SpringBootApplication@ControllerpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}@GetMapping("/hello")publicStringhello(){return"hello";}}
hello.html
<!DOCTYPE html><htmlxmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title>Hello, World!</title></head><body>
Hello, World!
</body></html>
$ gradle bootJar

Java は起動直後にメモリを大きく確保してしまうので、最小ヒープサイズに32MBを指定する。

$ java -Xms32m -jar build/libs/demo-0.0.1-SNAPSHOT.jar
$ curl http://localhost:8080/hello
$ ps au
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
hoge 20294   0.0  2.0  8094612 171024 s000  S+   10:09PM   0:17.35 java -Xms32m -jar build/libs/demo-0.0.1-SNAPSHOT.jar

Angularで簡単アニメーション:左からフェードイン編

$
0
0

犬クジラです。
初投稿の今回はAngularで簡単なアニメーションを動かす内容について投稿します。
これまで多くのWEBアニメーションはCSSを使って動かしていますがAngularでも簡単に作ることができます。
手順公開のためStackblitz上にて作業を行っています。

今回の完成デモ

movie.gif

app.module.tsにAnimationsモジュール追加

Angularでアニメーションを利用するにはBrowserAnimationsModuleが必要なので追加します。
モジュールの追加インストールが必要な場合は追加してください。

app.module.ts
import{NgModule}from'@angular/core';import{BrowserModule}from'@angular/platform-browser';import{BrowserAnimationsModule}from'@angular/platform-browser/animations';// 追加import{AppComponent}from'./app.component';@NgModule({imports:[BrowserModule,BrowserAnimationsModule// 追加],declarations:[AppComponent],bootstrap:[AppComponent]})exportclassAppModule{}

アニメーションで動かす対象を設定する

アニメーションがいつ開始するか検知するために、Angularはトリガーを必要とします。
trigger()関数は、変更を監視するためにプロパティ名を記述します。変更が発生すると、トリガーはその定義に含まれるアクションを開始します。ここではトリガー名を「fadein-left」をしておきます。

app.component.html
<div@fadein-left><div>テキスト</div></div>

コンポーネントファイル内にアニメーション関数をインポートする

今回は以下の関数を利用します。

app.component.ts
import{trigger,style,animate,transition,query,sequence,}from'@angular/animations';// …省略

アニメーションメタデータプロパティを追加する

コンポーネントファイル内の@Component()デコレーター内にanimations:というメタデータプロパティを追加してください。アニメーションを定義したトリガーをanimationsメタデータプロパティ内に配置します。その中に今回のトリガーとなる「fadein-left」を設定します。

app.component.ts
// …省略@Component({selector:'my-app',templateUrl:'./app.component.html',styleUrls:['./app.component.css'],animations:[trigger('fadein-left',[// -----------------------------// ここにアニメーション内容を書く// -----------------------------]),]})// …省略

動かす内容を書く

いよいよアニメーションさせたい内容を記述します。
今回のストーリーでは画面左からフワッとフェードインする流れになるので以下のように設定

app.component.ts
// …省略animations:[trigger('fadein-left',[transition('void => *',[sequence([// アニメーション1:初期表示位置は不可視化しておくquery('div',style({opacity:0,// 不可視化"margin-left":'-20px'})),// アニメーション2:スタート開始時間調整(ここでは1秒後に開始)query('div',animate('1000ms',style({}))),// アニメーション3:2秒かけて左から可視化されるquery('div',animate('2000ms ease-out',style({opacity:1,"margin-left":'0px',}))),]),]),]),]// …省略

アニメーション内容の補足として、「transition('void => *'」はHTML要素というかDOM内に指定のトリガーが現れた際に未定義("void")からは定義済み*(アスタリスク)に処理されたという解釈となり後続の処理が自動実行される仕組みとなります。queryはトリガー定義されているHTML要素内のdivに対して処理が行われます。

あとは簡単に

  1. 初期表示として移動元となる距離から不可視化の状態にします。
  2. アニメーションの開始ミリ秒を指定します。
  3. 実際にアニメーションが開始され2秒かけて左に移動しつつopacityによって可視化が行われます。

となるだけです。
非常に簡単ですね。

movie.gif

今回の完成デモ

初心者のためのRESTful

$
0
0

概要

web APIを設計するためのシンプルな標準の方法であるRESTfulの概要をメモとして残す。
またその際に重要となるhttpStatusCodeやCRUDの概要も追記する。

参考資料

オライリージャパン出版 Jonathon Rasmusson 著 玉川紘子 訳
「初めての自動テスト webシステムのための自動テスト基礎」

RESTfulとは

参考資料p67より

「ねえみんな、Webのリソースにアクセスする方法をそれぞれ独自に定義するんじゃなくて、探しているリソースの名前をURLとして定義して、リソースと通信するための操作を4つの動詞に絞ったらどうだろうか、つまり、GET、POST、PUT、DELETEの4つだ。」

RESTfulとはwebAPIを設計するときの方法またはその定石のことである。
| リソースに対する動作 | リソースの場所 |
|:-:|:-:|
| HTTP GET | /users/:id |
| HTTP POST | /users |
| HTTP PUT | users/:id |
| HTTP DELETE | users/:id |

リソースの場所に対してURLを決め、そのリソースに対する動作をするIDをつけることで、APIが完成する。これがRESTである。

HTTP ステータスコード

httpStatusCodeとはリソースに対しての動作が行われたとき、それをユーザに「正常に終了したのか」、「要求された情報が見つからなかったのか」、「サーバー側で何らかのエラー発生したのか」等をレスポンスに括り付けてユーザに教えるものである。

app.js
app.get('/users/:id',async(req,res)=>{try{ //ユーザーのリストからidを用いて探すconstuser=awaitUsers.findById(req.params.id)if(!user){//見つからなかったらres.status(404).send()}//正常に終了したres.status(200).send(user)}catch(e){//何らかのエラーが発生したres.status(500).send(e)}})

コードはNode.jsサーバーにおいて、URL内のidを用いてMongoDB内のリソースから該当するユーザを探し、該当する者がいれば200を返し、見つからなければ404を返し、何らかのエラーが発生すれば500を返している。

このようにステータスコードを理解することによって、サーバーがリクエストに対してどのような処理をしたのかがわかるようになる。
この3つのほかにも様々なhttpStatusCodeがある。

HTTPレスポンスステータスコード
https://developer.mozilla.org/ja/docs/Web/HTTP/Status

ここには様々なhttpStatusCodeが載っている。百の位の数字によって意味が分かれている。
初心者ながら他に私がよく使うもの、よく見るものは新しくユーザ登録をしたときに成功したことを示す「201 created」やコードが間違っているときに返される「400 bad request」である。

CRUDとは

CRUDとはcreate(作成)、read(読み出し)、update(更新)、delete(削除)という4つの動作を表すものである。

これはREASTfulにおいては下記のように対応している。

| GET | read |
| POST | create |
| PUT | update |
| delete | delete |

CRUD操作を行うAPIをもつとはこれらの操作を可能とするwebアプリケーションであるということである。

まとめ

RESTful、HTTPStatusCode、CRUDを理解することでどういったAPIを持ったwebアプリケーションを作っていよいかという作る前の疑問を解消してくれる。あとはこれらのAPIを備えたwebアプリケーションを作成するだけである。

タイプスクリプトでドメインオブジェクトやってみた

$
0
0

読んだ
現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法 Kindle版

成果物1
classShippingCost{readonlybasePrice:number;readonlyMINIMUM_FOR_FREE=3000readonlyCOST=500constructor(basePrice:number){this.basePrice=basePrice}amount():number{if(this.basePrice<this.MINIMUM_FOR_FREE)returnthis.COSTreturn0}}constABOVE_THRESHOLD:number=5000constBELOW_THRESHOLD:number=300constabove_threshold_cost:ShippingCost=newShippingCost(ABOVE_THRESHOLD)console.log(above_threshold_cost.amount())// -> 0// 3000円以上の注文なので送料0円constbelow_threshold_cost:ShippingCost=newShippingCost(BELOW_THRESHOLD)console.log(below_threshold_cost.amount())// -> 500// 3000円未満の注文なので送料500円

送料のみを求めるクラス
外側から送料を変更できない(readonly)


成果物2
classQuantity{readonlyMIN:number=1readonlyMAX:number=100readonlyquantity:numberconstructor(quantity:number){if(quantity<this.MIN)thrownewRangeError("不正:"+this.MIN+"未満")if(quantity>this.MAX)thrownewRangeError("不正:"+this.MAX+"超過")this.quantity=quantity}canAdd(quantity:Quantity){constadded:number=this.addValue(quantity)returnadded<=this.MAX}add(quantity:Quantity){if(!this.canAdd(quantity))thrownewRangeError("不正:合計が"+this.MAX+"超過")constadded:number=this.addValue(quantity)returnnewQuantity(added)}privateaddValue(quantity:Quantity){// privateはちょっとprivateらしいreturnthis.quantity+quantity.quantity}}constaddedQuantity:Quantity=newQuantity(40)console.log(addedQuantity.quantity)letbelow:Quantity=newQuantity(50)console.log(addedQuantity.canAdd(below))// -> trueletabove:Quantity=newQuantity(90)console.log(addedQuantity.canAdd(above))// -> falseaddedQuantity.add(below)// readonlyなので変更が行われない(エラーが出ないのは良くないと思った)console.log(addedQuantity.quantity)// -> 40

値が範囲内に収まるか?or値を追加するクラス
しきい値の変更ができない(readonly)
初期化時に範囲外ならエラー

readonlyquantity:numberquantity:number

変更すれば、MAXまでは追加できるが、超過するとエラー


細かく区切れば、変更しやすいとか利点がある。
意識していきたい。
極端に言えば以下でも成り立つわけで、、、意識していきたい

classQuantity{quantity:numberconstructor(quantity:number){this.quantity=quantity}}

CognitoUserPoolの情報をLambda経由でRDSへ連携した

$
0
0

前書き

勉強用に何かしらのウェブサービスを作ろうと常々考えていたのですが、やはり認証基盤を自分で一から作るのは手間が非常にかかるので足踏みをしていました。
そこでCognitoを使おうと思ったのですがサーバーレス構成もNOSQLも使ったことがないのとNOSQL辛いよというアドバイスを頂いたので、とりあえず作るということを優先して日頃なれているRDBに認証情報を放りこみたかったという感じです。
なお技術スタックとしてLambdaではNode.js、RDSにはPostgreSQLを採用しました。

流れ

  1. CognitoのイベントをLambdaでフックする
  2. LambdaでCognitoの認証情報を取得する
  3. LambdaからRDSへデータを書き込む

1. CognitoのイベントフックをLambdaで行う

Cognitoはサインアップ前確認後などそれぞれのイベントに合わせてLambdaでフックすることができます。
今回のユースケースでは確認後に行うのが適切でしょうか。今回私は確認後で設定しました。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html

2. LambdaでCognitoの認証情報を取得する

Cognitoのイベントフックとして使用した場合、Lambdaのevent引数に認証情報が渡されます。
以下のJson形式で渡される共通パラメータに加えて、各イベントごとに固有のパラメータが追加されます。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html#cognito-user-pools-lambda-trigger-event-parameter-shared
確認後イベントの場合、event.request.userAttributes以下にユーザープールの属性値が入ってくる形になります。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html#w208aac15c27c23c15b9b1
今回私はuserName情報とemail情報を書き込んだので、event.userNameとevent.request.userAttributes.emailになりますね。

3. LambdaからRDSへデータを書き込む

LambdaからPostgreSQLへ接続する

今回のLambdaのためにNode.jsを初めていじったので全く詳しくないのですが、Node.jsからPostgreSQLへ接続するにはpgパッケージを用いるのが一般的なようです。
ただLambda内でnpm installやyarn addを行うことはできないので、別の環境で用意したnodeModulesをLambdaへアップロードする必要があります。
様々な方法があるようなのですが、今回はローカルで作成したコードを雑にzipで固めてアップロードしました。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-deployment-package-nodejs/

LambdaからRDSへのアクセス制御

細かい話ですがRDSはVPC内に存在しているのでLambdaからアクセス可能にしておかなければなりません。
今回はRDSとLambdaをデフォルトVPCに所属させることとしました。

成果物

以下のリポジトリです
https://github.com/inobu/cognito-rds-associate

Raspberry Pi 4+Chinachu v0.10.1-gamma.0 地デジ録画サーバー構築

$
0
0

はじめに

Raspberry Pi 4が手に入って色々と遊んでいたタイミングでちょうどよく(?)ブルーレイレコーダーが壊れたので、買い直すくらいなら自作するかという事で今回地デジ録画サーバを構築した時のメモです.

ひとまず録画してエンコードできれば良いのでストリーミング再生手順は省略。

なお、本記事は以下の記事を参考に環境構築しています。
https://qiita.com/shotasano/items/3809b8f3e0b62d51d3c3

⚠︎注意

以下の手順は全て自己責任でお願いします。
テレビ番組のコピー、配信は違法です。私的利用だけにして下さい。
TS抜き自体が現状グレーなのでそこらへん理解した上で利用できる方だけ利用して下さい。
この記事を参考にしたことにより生じたあらゆる損害について筆者は一切の責任を負いません。

使用ハード

Raspberry Pi 4 Model B / 4GB

Raspberry Pi 3でも同じ手順でいけます。
Raspberry Pi 4ではCPU性能上がったのでエンコード速度が多少向上してます。

PLEX USB接続型フルセグ対応地上デジタルTVチューナー PX-Q1UD

PX-S1UD V2.0 でも可。違いはチューナーの数(同時録画出来る番組数)。
PT3 では構築してませんがdriverが違うのでそこら辺の手順が変わります。

SCM ICカードリーダー/ライター SCR3310/v2.0

B-CASが読めるカードリーダー

アンテナ分配器 S-A2SP05WH4C TAROS

TVに出力する線とチューナーに接続する線を分ける分配器。
くれぐれも間違えて分波器を買わないように。
電流通過型であれば何でも良いですが、間に別のレコーダーが入っていたりするとケーブルがない版では構造的に上手く接続出来なかったりするかも。

B-CASカード

これがないと地デジ信号デコード出来ません。
今回は壊れたブルーレイレコーダーから拝借。
カードだけ欲しい場合は、B-CAS社経由で再発行(2,160円)出来ます。

アンテナケーブル

ブルーレイレコーダーで使ってたものを使用。

HDD

そこら辺に転がっていたのを適当に使用。
先日アキバに行ったら「WD Blue 4TB」辺りが7K円くらいで売ってました。

HDDケースに関しては「電源連動"しない"」ケースの方が良いです。
電源連動ケースではラズパイが起動しても電源連動でONになってくれなかったりします。
電源連動しないケースは価格.comのここらへんが参考になるのかな。知らんけど。
https://kakaku.com/specsearch/0539/

Raspberry Piの準備

OSインストール

時間はかかりますが特に詰まるところはありません。
今回は以下の記事を参考にしました。
https://pcmanabu.com/raspberry-pi4-install/

Raspbion Fullをインストールしましたが、GUI使わないので Raspbion Liteでも良かった気がします。
ラズパイ4からWi-fi 5GHzに対応したのが良いですね。

Wi-fi設定

有線LANで利用するなら不要な手順です。
ラズパイ起動時にWi-Fiに自動で接続するように設定します。

SSIDを検索

$ sudo iwlist wlan0 scan | grep ESSID

たまにbusyになったりしますが、大抵再実行すればいけます。

SSIDを追加

$ sudo raspi-config
> 2 Network Options
> N2 Wi-fi
> SSIDを入力
> SSIDに対応するパスフレーズを入力

WPAサプリカントに追加

$ sudo sh -c"wpa_passphrase <接続先SSID> <接続先SSIDのキー> >> /etc/wpa_supplicant/wpa_supplicant.conf"$ sudo cat /etc/wpa_supplicant/wpa_supplicant.conf

Wi-Fiインターフェース再起動

$ sudo ifdown wlan0
$ sudo ifup wlan0

繋がらない場合はDHCPからIPアドレスを再発行。

$ sudo dhclient wlan0

IPアドレス確認

$ ifconfig

IPアドレス固定化

IPアドレスを固定化しておかないとリモートから接続する際に困るので固定化しておきます。

ルーターのIPアドレスを事前に調べておく必要があります。
IO-DATA製ルーターなら 192.168.0.1
ASUS製ルーターなら 192.168.1.1
NEC製ルーターなら 192.168.10.1
Buffalo製ルーターなら 192.168.11.1
辺り。

DHCPでのIPアドレス固定設定

$ sudo nano /etc/dhcpcd.conf

ファイルに以下の設定を追加。

interface wlan0
static ip_address=<割り当てるIPアドレス>/24
static routers=<ルータのIPアドレス>
static domain_name_servers=<ルータのIPアドレス>

SSH接続設定

リモートからSSH接続するための設定です。
ラズパイはdefaultではSSHでの接続は無効化されています。

$ sudo raspi-config
> 4 Interfacing Options
> P2 SSH
>[はい]

再起動

SSH接続設定まで終わったら一旦再起動し piユーザでリモートから接続できるか確認。
piユーザのパスワード設定するの忘れていたら raspi-configから設定可能。

sudo shutdown -r now

HDDマウント

HDD確認

$ sudo fdisk -l | grep /dev/sda
> Disk /dev/sda: 3.7 TiB, 4000787030016 bytes, 7814037168 sectors

2ポートある USB 3.0 の上は /dev/sda, 下は /dev/sda1である模様。

HDDフォーマット

フォーマットするとHDDの中身が消えるので注意。

# パーティションテーブルの設定$ sudo fdisk /dev/sda

# d でパーティションを削除$ Command (m for help): d

# p で現在の状態を確認$ Command (m for help): p

# n でパーティションを作成# パーティション分けないなら全部デフォルトで良い$ Command (m for help): n

# p で現在の状態を確認$ Command (m for help): p

# w で変更を書き込み$ Command (m for help): w
> The partition table has been altered!
> Calling ioctl() to re-read partition table.
> Syncing disks.

FileSytem を ext4にフォーマット

Linuxシステムに最適化するためFileSytemを ext4にフォーマットします。
フォーマットするとWindowsでは認識できなくなります。

$ sudo mkfs.ext4 /dev/sda
> Allocating group tables: done> Writing inode tables: done> Creating journal (32768 blocks): done> Writing superblocks and filesystem accounting information: done

再起動時に自動的にマウントされるように設定

HDDの UUIDをメモしておく。

$ sudo blkid /dev/sda
> /dev/sda: UUID="92720b54-5f27-4367-8271-469eeee7caba"TYPE="ext4"

HDDが /mnt/hdd1に自動マウントされるよう fstabを更新。

# バックアップ$ sudo cp /etc/fstab /etc/fstab.bak

# fstabを編集しマウント設定を追加$ sudo nano fstab

# デバイス名 マウントポイント ファイルシステム オプション dump指定 fsck指定UUID=92720b54-5f27-4367-8271-469eeee7caba /mnt/hdd1 ext4 nofail 0 0

# 再起動$ sudo reboot

# HDDがマウントされたか確認$ df-h | grep /mnt/hdd1
> /dev/sda         3.6T   0G  3.6T    0% /mnt/hdd1

ユーザ作成

$ sudo adduser <追加ユーザ>
$ sudo gpasswd -a<追加ユーザ> sudo$ sudo adduser <追加ユーザ> video

ラズパイデフォルトのユーザ piはセキュリティ的にあまり使いたくないのでユーザを作成。
sudo出来るように sudoグループに追加しておきます。
そのままだと結局 rootになれてしまうので 後で sudoの細かい設定を別途追加してください。

videoグループに所属しておかないとChinachu経由で録画出来ないので設定しておきます。

samba設定

ラズパイのHDDを外部から覗けるようにsambaを設定。

sambaのインストール

$ sudo apt-get update
$ sudo apt-get install samba

sambaの設定

sambaをHDDのルートに紐付けます。
別のパスにしたい場合は pathを変更して下さい。

$ sudo nano /etc/samba/smb.conf
# 以下の設定を追加[raspberrypi]
   comment = Raspberry Pi
   path = /mnt/hdd1
   guest ok =yes
   read only = no
   browsable = no
   force user =<追加ユーザ>

# サービス再起動$ sudo service smbd restart

# ディレクトリのオーナーを変更$ sudo chown<追加ユーザ>:video /mnt/hdd1

samba接続確認

\\<ラズパイのipアドレス>\raspberrypi\
ラズパイのディレクトリを覗けることを確認。

チューナーの準備

PLEXチューナのdriverインストール

$ wget http://plex-net.co.jp/plex/px-s1ud/PX-S1UD_driver_Ver.1.0.1.zip
$ unzip PX-S1UD_driver_Ver.1.0.1.zip
$ sudo cp PX-S1UD_driver_Ver.1.0.1/x64/amd64/isdbt_rio.inp /lib/firmware/

カードリーダーとB-CASの準備

driverインストール

$ sudo apt-get install build-essential git
$ sudo apt-get install pcscd libpcsclite-dev libccid pcsc-tools

カードリーダーの型番確認

$ lsusb

カードリーダーを認識しない場合はラズパイ再起動やUSBの差し直しを試してみて下さい。
一旦ラズパイからUSBを外して再起動してからまた差し直すと良いかも。

参考にした元記事の方を見てるとたまにSCM製以外のカードリーダーが混じるらしい。
その場合は /etc/libccid_Info.plistに定義を追加する必要があります。
詳細は元記事の方を参照してください。

B-CASが読み取れるか動作確認

$ sudo pcsc_scan
> 0: SCM Microsystems Inc. SCR 3310 [CCID Interface] (XXXXXXXXXXXXX) 00 00
> ...中略...
> Japanese Chijou Digital B-CAS Card (pay TV)

B-CASデコード用ライブラリのインストール

$ sudo apt-get install cmake g++
$ wget https://github.com/stz2012/libarib25/archive/master.zip -O libarib25.zip
$ unzip libarib25.zip
$ rm libarib25.zip
$ cd libarib25-master
$ cmake .$ make
$ sudo make install

録画用コマンドツールのインストール

録画コマンドにrecdvbを使用します。

インストール

$ wget http://www13.plala.or.jp/sat/recdvb/recdvb-1.3.2.tgz
$ tar xvzf recdvb-1.3.2.tgz
$ cd recdvb-1.3.2
$ ./autogen.sh
$ ./configure --enable-b25字幕やデータ放送のデータが不要な場合は
$ make
字幕やデータ放送のデータが必要な場合は
$ EXTRA_SID=1 make
$ sudo make install

上手く行っていれば上記の時点で地デジ録画可能になりました。

動作確認

driverとrecdvbコマンドが導入されチューナが認識されていれば以下のコマンドで録画出来ます。

# $ recdvb [--dev <チューナ番号>] --b25 --strip チャンネル番号 録画秒数 録画ファイル名$ recdvb --b25--strip 22 10 /mnt/hdd1/test.m2ts
> ...中略...
> Recording...
> Recorded 10sec

VLCなどで開いて問題なく録画出来ているか確認して下さい。

Mirakurun (チューナーサーバー) 設定

チューナーサーバーをインストールすると録画コマンドの並列化やチューナーの管理を自動で行ってくれ、LAN内でSSH接続しなくてもチューナー自体を操作できるREST APIが提供されます。

NodeJS v10.16.3 インストール

Mirakurun は NodeJSで動作するサーバなのでNodeJSをインストールします。
Mirakurunは NodeJS v12.x でも動作しましたが、Chinachu v0.10.1-gamma.0の方が v10.16.3を利用するため当バージョンをインストールします。

普通に指定バージョンのNodeJSをインストールしても良いですが、今回は複数バージョンを管理出来るよう nを利用します。

$ sudo apt-get install npm
$ sudo npm install-g n
$ sudo n 10.16.3
$ sudo npm install-g npm

npmに関しては定期的にJVNに脆弱性が報告されてくる最新版にしておくのが吉。

Mirakurun インストール

$ sudo npm install pm2 -g$ sudo npm install mirakurun -g--unsafe--production

Mirakurun チューナー設定

チューナー別録画コマンド設定を行います。
nameは何でもOK。
types設定のGRは地上波、BSはBS、CSはCS、SKYはスカパー。
commandで録画を行うコマンドを設定します。

$ sudo EDITOR=nano mirakurun config tuners

# 以下の設定を追記。# PX-Q1UD は 4チューナーあるので4つ設定。# PX-S1UD V2.0 の場合は 1チューナーなので --dev x オプションを外した1つだけで大丈夫。

- name: PX-Q1UD-1_1
  types:
    - GR
  command: recdvb --b25--strip--dev 0 <channel> - -

- name: PX-Q1UD-1_2
  types:
    - GR
  command: recdvb --b25--strip--dev 1 <channel> - -

- name: PX-Q1UD-1_3
  types:
    - GR
  command: recdvb --b25--strip--dev 2 <channel> - -

- name: PX-Q1UD-1_4
  types:
    - GR
  command: recdvb --b25--strip--dev 3 <channel> - -

チャンネルスキャン

channels.ymlを手動更新しても良いですがスキャンするのが手っ取り早いです。
Mirakurun の channels scan API を叩きます。

$ curl -X PUT "http://localhost:40772/api/config/channels/scan"

Chinachu (録画サーバー) 設定

MirakurunだけではAPIが提供されただけなのでまだ使いにくい
そのためMirakurunで動作する録画サーバーのChinachuを導入します。
Chinachuは最新の番組表の取得を自動で行い、録画予約もブラウザから操作出来るようになります。

インストール

$ sudo apt-get install build-essential curl git-core vainfo

# chinachu は割と容量食うので外付けHDDにインストールしておきます。$ git clone git://github.com/kanreisa/Chinachu.git /mnt/hdd1/chinachu
$ cd /mnt/hdd1/chinachu
$ ./chinachu installer
> 1. Auto を選択

# 録画ルールの初期ファイルを作成# 作成しない場合はアニメと笑点辺りを自動で録画するルール(rules.sample.json)が設定されます。$ echo"[]"> rules.json

# 録画設定ファイルの作成$ cp config.sample.json config.json

# uid, recordedDir, temporaryDir, recordedFormat 辺りを編集。$ nano config.json
  "uid": "<追加ユーザ>""recordedDir" : "/mnt/hdd1/recorded/",
  "temporaryDir" : "/mnt/hdd1/recordtmp/",
  "recordedFormat": "[<date:yymmdd-HHMM>][<channel-name>]<title> <subtitle> <episode:2>.m2ts",

NodeJSのシンボリックリンク作成

.nave/installed/以下が空の場合はNodeJSのバイナリに向けてシンボリックリンクを作成しておく必要があります。

# node のシンボリックリンク先を確認$ cd .nave
$ ls-la> node -> /mnt/hdd1/chinachu/.nave/installed/10.16.3/bin/node
> npm -> /mnt/hdd1/chinachu/.nave/installed/10.16.3/bin/npm

# n でインストールした上記と一致するバージョンのnodeへのシンボリックリンクを作成$ cd installed/
$ ls-s /usr/local/n/versions/node/10.16.3 10.16.3

Chinachuの起動確認

$ cd /mnt/hdd1/chinachu/
$ ./chinachu service wui execute
Ctrl+Cで停止

ファイルが不足しているエラーが出る場合はGithubからファイルを取得して下さい。

Chinachuの起動とサービス登録

$ sudo pm2 start processes.json
$ pm2 save

Chinachuを使う

以下URLでChinachuのGUIにアクセスできるようになりました。

http://[ラズパイIPアドレス]:20772/

chinachu.PNG

自動でエンコードする

tsファイルのままではファイルサイズがデカすぎるのでffmpegを使用してエンコードします。

ディレクトリ作成

以下のディレクトリを作成。

  • エンコード中の作業ディレクトリ: /mnt/hdd1/processing
  • エンコード完了ディレクトリ: /mnt/hdd1/encoded

スクリプト作成

ラズパイ4ではハードウェアエンコーダ h264_omxが使えるので
ffmpegh264_omxを使用してエンコードを行います。

#! /bin/bash# デノイズやインターレース解除のフィルタを当てる-vf pp=acが重いので外すのもアリ。# フレームレート 23.98 はアニメ向け。実写なら 29.97 くらい。# 画質上げたいならビットレート -b:v 3000k 辺りの数字をいじる。X264="-f mp4 -c:v h264_omx -r 23.98 -aspect 16:9 -s 1440x1080 -b:v 3000k -threads 0 -flags +ilme+ildct -top -1 -deinterlace -vf pp=ac -y"# -crf でうまい具合にエンコード出来ないか調整中#X264="-f mp4 -c:v h264_omx -r 23.98 -aspect 16:9 -s 1440x1080 -flags +ilme+ildct -top -1 -deinterlace -crf 23 -qcomp 0.75 -psy-rd 1:0.2 -deblock 0:0 -partitions p8x8,b8x8,i8x8,i4x4 -y"DIR1="/mnt/hdd1/recorded"DIR2="/mnt/hdd1/processing"DIR3="/mnt/hdd1/encoded"EXT="m2ts"ENCEXT="mp4"orgfpath=`find $DIR1-maxdepth 1 -type f -name"*.$EXT" | awk'NR==1' | awk'{printf $0}'`# エンコード対象ファイルがない場合はスキップif![-e"$orgfpath"];then
        exit 0
fi

orgfname="${orgfpath##*/}"encfname="${orgfname%.*}.$ENCEXT"echo"orgfile=$orgfname"echo"encfile=$encfname"# エンコードは2プロセスまで許すcount=`ps -ef | grep ffmpeg | grep-vgrep | wc-l`echo"encoding count $count"if[$count-lt 2 ];then
        mv"$orgfpath""$DIR2"
        ffmpeg -i"$DIR2/$orgfname"$X264"$DIR2/$encfname"mv"$DIR2/$encfname""$DIR3"# 元動画の削除は一旦画質に納得してからの方が良さそう# rm -f "$DIR2/$orgfname"fi
echo"end"

動作確認

$ sudo chmod 755 encode.sh
$ ./encode.sh

cronで定期的に実行する

# rsyslogを編集# cron のコメントアウトを外す$ sudo nano /etc/rsyslog.conf
cron.*              /var/log/cron.log

# rsyslogの再起動$ sudo /etc/init.d/rsyslog restart

# cronログレベルの設定# 全部ログ出す場合は15$ sudo nano /etc/default/cron
EXTRA_OPTS="-L 15"# cronデーモンの再起動$ sudo /etc/init.d/cron restart

# crontab設定$ crontab -u<実行ユーザ> -e# 4持~10持の間、30分毎に実行する場合*/30 4-10  ***     /mnt/hdd1/tools/encode.sh

感想

Mirakurun, Chinachu も安定してきて特に詰まるところ無く動きました。
Mirakurunのストリーミング再生は今のままでは動かないのでまた追加で設定する必要がありそうです。
recdvbの方のストリーミング再生を試してみましたが数秒の遅延でそれなりに見られる感じ、ただしずっと見ているとたまにフリーズします。
ラズパイで組む場合は録画専用サーバーとして割り切るのが良いかと。


iPhoneから1タップするだけでAWS上のプログラムをON/OFFする方法

$
0
0

はじめに

常にクラウドでプログラムを走らせておいて、
自分の気の向くままに、どこでもいつでも手軽にプログラムを停止、再起動したい時ってありませんか・・?
私はあります!

わざわざon/offするだけなのに外出先にノートPCを持ち歩きたくない、
iPhoneひとつで、もしくはAppleWatchからポンと押すだけで実行・停止できる。
なんかかっこいい、、

iPhoneや、AppleWatchに「ヘイ Siri! ホゲをつけて」
と言うだけでAWSプログラムを起動できます。
ナンカ、カッコイイデス。

「したいもの&カッコいいものは作ればいい」ということで早速作ってみました。

仕様条件

  • クラウドはAWSのEC2
  • 実行停止するプログラムはNode.js
  • 自宅にネット接続されたラズパイが常駐している事
  • Apple HomeKit環境を自宅で設定できること
  • iPhoneユーザーであること

概要

早速、ネタバラシします。
AppleにはIoT家電を制御する環境としてHomeKitがあります。
Apple HomeKitの説明

家の中はもちろん、外出先からも自宅の家電を操作することができる優れモノですが、その特性を利用します。
まず、Node.jsのライブラリhomebridgeを利用して自宅のラズパイをHomeKitで認識できるIoT家電化させます。
次に、IOT家電化された自宅のラズパイを通して、AWSに接続し、コマンドを要求します。
AWSはラズパイからのコマンドを実行することで、AWSクラウド上のプログラムをON/OFFします。

以上、パチパチパチヽ(`▽´)/
それでは細かく見ていきます。

homebridgeをラズパイにセッティング

homebridgeはNode.jsライブラリで、インストールしてdaemon化することで
ラズパイをHomeKitのブリッジにエミューレートさせることができます。

homebrigeの詳しい説明とセッティングに関しては以前の記事を御覧ください
Philips_HueをAPI連動!〜ラズパイをHomeKit化する

さて、セッテイングできましたら、
homebrideのconfigにon/offした時に何をさせたいのかコマンドを設定します。
今回は、AWSに命令するshellコマンドをon,offに設定します。

config.json
#以下を該当箇所に追加#/home/pi/は適宜読み替えてください"accessories":[{"accessory":"CMD","name":"ホゲ","on_cmd":"/home/pi/startAWS.sh","off_cmd":"/home/pi/stopAWS.sh"} ]
  • "name":ボタンの名前になります。Siriに話しかけるので恥ずかしくない名前にしましょうw
  • "on_cmd":スイッチのON状態、Siriの「つけて」状態で実行されるコマンドです。
  • "off_cmd":スイッチのOFF状態、Siriの「消して」状態で実行されるコマンドです。

homebridgeのセッティングはひとまず以上となります。

肝心のShellファイルの内容ですが、一旦保留して
shellファイルの命令を受けるAWSのセッティングを先におこないます。

AWSの準備:foreverをインストール

AWSのEC2サーバーを使用します。
EC2サーバーには事前にNode.js、npmのインストールしておきます。
私はLinuxOSはubuntu18.04LTSで構築、nodebrewでNode.jsをインストールしています。
OSはAmazon Linuxでも、Node.jsのインストールも公式通りで問題ないと思います。
適宜読み替えてください。

AWS上でNode.jsプログラムを常時実行させて再スタート、終了を制御したいので、
今回は使い勝手のよいライブラリforeverを使用しました。

foreverは
Node.jsで作られたプログラムをデーモン化して簡単に常駐実行することができます。
ラズパイからAWS上のforeverに命令することで、Node.jsプログラムのON/OFFをするというカラクリです。

AWSにforeverをインストールします。

npm install-g forever

なお、foreverの基本的な使い方は以下のブログが詳しいです。
foreverの使い方とデーモン化による永続化・自動起動まとめ!

AWS側はひとまずこれだけです。
あっ、当然ですが実際に起動したいNode.jsプログラムもAWSに保存しておきます。

shellファイルの設置

自宅のラズパイから、AWSにコマンド命令するshellファイルを準備します。

コマンド内容はこれだけです。

  • ssh接続
  • foreverコマンドでスタート、ストップ

ラズパイからAWSにssh接続するので、事前にラズパイにEC2接続用のpemファイルを保存しておく必要があります。

startAWS.sh
#!/usr/bin/env bash#0.0.0.0は任意のEC2のipアドレス
ssh -i ~/.ssh/aws.pem ubuntu@0.0.0.0 <<EOC
/home/ubuntu/.nodebrew/current/bin/node /home/ubuntu/.nodebrew/current/bin/forever start -l hoge.log -a /home/ubuntu/hoge/hoge.js
EOC
stopAWS.sh
#!/usr/bin/env bash

ssh -i ~/.ssh/aws.pem ubuntu@0.0.0.0 <<EOC
/home/ubuntu/.nodebrew/current/bin/node /home/ubuntu/.nodebrew/current/bin/forever stop /home/ubuntu/hoge/hoge.js
EOC

startAWS.shをもとに解説

  • sshで接続し、コマンドを実行させる
  • aws.pemはラズパイ上のpemファイル
  • ubuntu@0.0.0.0はAWS EC2の任意のユーザー名とipアドレスに変更してください
  • /home/ubuntu/.nodebrew/current/bin/node
    直接コマンドを流し込むのでEC2サーバーのENVが読まれずforeverから始めるとエラーになります。(foreverのshebangが通らない) foreverを動かすインタプリタとしてnodeを指定する必要があります。
    またENVが読まれないのでフルパスでnodeを指定します。
  • /home/ubuntu/.nodebrew/current/bin/forever
    上記と同様でフルパスでforeverを指定
  • start常時稼働を開始
  • -l hoge.logログファイルをhoge.logに指定。
  • -aは上書きではなく、追記でログを書き込むオプション(Append logs)
  • /home/ubuntu/hoge/hoge.jsこちらが実際に実行・停止したいプログラム本体です。

上記の、startAWS.shとstopAWS.shをラズパイに保存して
homebridgeのconfigに指定すれば準備完了です。
お疲れさまです:D

流れを見てみる

それでは全てを踏まえて、もう一度流れを確認します。

  1. iPhone,AppleWatchの「ホーム」から該当スイッチをタップ(もしくはSiriに呼びかけ)
  2. AppleHomeKitにより自宅ラズパイでdaemon化したhomebridgeからshellファイルが実行される
  3. shellファイルからAWSにssh接続して、foreverコマンドがAWSに渡される
  4. AWS上のforeverを利用してhoge.jsをrestart,もしくはstopする

こんな感じです。

おまけ

外出先でハッキリとONされたのを確認したいなら、
予め、プログラムにlineNotifyを仕込んでおくと、スタートされたらLineでpushが来るようにできて分かりやすいです。
参考:LINE notify を使ってみる

おわりに

遠隔操作は無数に方法があると思うのですが、とにかく手軽に作りたかった。

foreverでなく、ubuntuのsystemctlで常駐操作しても良かったのですが、
設定ファイルを書くのが面倒くさかったり、手軽に実行ファイルを変更できないので見送りました。
ただ、foreverだとNode.jsに限定されるので
他の言語でカッチリ作りたい場合はsystemctlしてもよいのではないでしょうか。

また、HomeKitの利点は手軽な操作だけでなく、
固定IPにしなくても自宅のラズパイにインバウンドできることでもあります、
この点もすごくお手軽です。

手軽にAWSプログラムをON/OFFしたい方のご参考になれたら嬉しいです。
ここまでお読み頂きありがとうございました。

追伸:
渋谷の人混みの中、AppleWatchに向かって
「へい、Siri! ホゲを起動して!」
と大きめの声で言うのは、ちょっと(だいぶ)恥ずかしいですw

Google Cloud Storage にデータがアップロードされたらブロックチェーンNEMに記録するFunctionsを作成する。

$
0
0

ブロックチェーンは改ざんが難しいプラットフォームであることはだんだんと周知されてきました。ですが、ブロックチェーンに登録する作業やシステム構築を考えた場合に、なかなか現在のシステムを置き換えるという発想には至っていないのが現状ではないでしょうか。
NEMはノードにREST API機能が実装されており、従来のシステムに「プラス・ブロックチェーン」を簡単に実現することが可能です。

今回はGCPが提供するGoogle Cloud Storageにデータがアップロードされた場合に、自動的にNEMブロックチェーンに記録するプログラムを作成してみましょう。

Google Cloud Storage にデータが登録された場合に起動させるファンクションの作り方は公式のチュートリアルが参考になります。
Cloud Storage のチュートリアル

これにNEM Catapultのライブラリを追加していきます。
nodejs-docs-samples/functions/helloworld/helloGCSGenericを改造してhelloNEMを作成してみました。

helloNEM
const{TransferTransaction,Deadline,Mosaic,MosaicId,UInt64,NetworkType,TransactionHttp,Account,PlainMessage}=require("nem2-sdk");exports.helloNEM=(data,context,callback)=>{constfile=data;constalice=Account.createFromPrivateKey("BCC06E6531609CABB77D33C6B59452CDB25E6485769913ACCC0E16C631DB7F36",NetworkType.TEST_NET);constNODE='https://jp5.nemesis.land:3001';constGENERATION_HASH="CC42AAD7BD45E8C276741AB2524BC30F5529AF162AD12247EF9A98D6B54A385B";consttx=TransferTransaction.create(Deadline.create(),alice.address,[newMosaic(newMosaicId('75AF035421401EF0'),UInt64.fromUint(0))],PlainMessage.create('Hello GCP World!'),NetworkType.TEST_NET,UInt64.fromUint(100000));constsignedTx=alice.sign(tx,GENERATION_HASH);consttxHttp=newTransactionHttp(NODE);txHttp.announce(signedTx).subscribe(_=>console.log(_),err=>console.error(err));console.log(`  Event: ${context.eventId}`);console.log(`  Event Type: ${context.eventType}`);console.log(`  Bucket: ${file.bucket}`);console.log(`  File: ${file.name}`);console.log(`  Metageneration: ${file.metageneration}`);console.log(`  Created: ${file.timeCreated}`);console.log(`  Updated: ${file.updated}`);callback();};

package.jsonにnem2-sdkの部分のみ追加します。

package.json
"dependencies":{"@google-cloud/debug-agent":"^4.0.0","escape-html":"^1.0.3","pug":"^2.0.3","supertest":"^4.0.2","nem2-sdk":"^0.16.1"}

デプロイします。

gcloud functions deploy helloNEM --runtime nodejs8 --trigger-resource staging.xxx.appspot.com  --trigger-event google.storage.object.finalize

ファイルをアップロードしてみましょう。

gsutil cp test/test2.txt gs://staging.xxxx.appspot.com

ログが出力されました。NEMのネットワークに到達です。

TransactionAnnounceResponse {
    message: 'packet 9 was pushed to the network via /transaction' }

ノードからトランザクション履歴を参照してみます。

[{"meta":{"height":"138477","hash":"C383A6ACDA343486E983B72EB49DF2797914008C6C26DE2ED54FD8051DABD629","merkleComponentHash":"C383A6ACDA343486E983B72EB49DF2797914008C6C26DE2ED54FD8051DABD629","index":0,"id":"5E12A75C43956B2E164B4B1F"},"transaction":{"signature":"2959553AC6AD908063947431A55DA9F7C6FBDFA58C6BB533FEBBC82209C29E2AE41309907BDF608C6ABD66AE7992951ADAE8D349440E2DCAF7B72FDF21EC860F","signerPublicKey":"62488F7A6C6F48274394C06FECD96AA2E467D9981D16E9C19066E846C9D5848A","version":1,"network":152,"type":16724,"maxFee":"100000","deadline":"4857588334","recipientAddress":"983E665F184685E44F89C8D7D5B65FF947215A46098A0455FC","message":{"type":0,"payload":"48656C6C6F2047435020576F726C6421"},"mosaics":[{"id":"75AF035421401EF0","amount":"0"}]}}]

無事登録成功です。

最後に

今後、ブロックチェーンと外部データとの連携は重要なテーマとなります。なぜなら、「ブロックチェーンに登録される前に改ざんされていない」という証拠があって初めて価値を持つ観測データが多くあるからです。今回はGCP上で設定されたセキュリティ基準を満たしたプラットフォームに上げさえすれば、あとはNEMブロックチェーンによって耐改ざん性が保証されるという可能性を提示しました。

Auth0+ファイル1つでローカルサイトにログイン機能を付ける!

$
0
0

小さいな1歩から始めることにしました。

前回、超初心者がログイン機能のついたサイトのデスクトップアプリ化に挑戦(あんど失敗・・)でAuth0で認証を付けた自分のサイトをエレクトロン化するとログイン機能が消えるという問題にぶつかりました。
色々悩みましたが、Auth0が提供してくれるサンプルファイルは多くあって、複雑すぎて何が間違っているかさっぱり見当がつきません・・(涙)。
そんな時、「ファイル一つコピペで試すAuth0+Vue.jsのログインサンプル」という記事を読んだので、コードを参考にしながら単純なファイルを用いて、ローカルサイトに認証(Line限定)を付けてみることにしました。
image.png

環境

Windows 10
Node.js v12.4.0
Visual Studio Code version 1.41
Live Server 5.6.0

方法

Step1. 好きな名前のフォルダを作ったらVisual Studio Codeを立ち上げます。
Step2. Visual Studio CodeでLive Serverをダウンロードします。
Step3. 「ファイル一つコピペで試すAuth0+Vue.jsのログインサンプル」を参考に、
    Index.htmlファイルを作成します。
Step4. Visual Studio Code右下の「Go Live」をクリック、
どこにつながるか確認しておきます。念のため、一旦Go liveは終了。
image.png
Step5. Auth0で認証用のアプリを作成します。(コチラの方法1~3)
Step.6 Auth0のApplication SettingsにてAllowed Callback URLs、
    Allowed Web Origins、Allowed Logout URLsをStep4で確かめた
    ローカルサイトに変更し、Saveします。
Step7. Auth0のDomain及びClint IDをIndex.htmlの該当箇所にコピーします。
Step8. 再度、「Go live」をクリックし、ローカルサイトで認証機能が付くか確認。

結果

あ~、できた!!
【トップ画面】
image.png

【ログイン画面)
image.png

感想

またもやQiita&先人の知恵に助けられました。
数週間悩んで体感したことは複雑なコードはレベルの高いことが初心者でもチャレンジできるが、逆に壁にぶち当たるとほぼ何もできないこと。
私でも構造が理解できるこの方法で次はエレクトロン化に挑戦したいと思います。

VSCode で "Cannot find runtime 'node' on PATH. Is 'node' installed?" との戦い

$
0
0

Cannot find runtime 'node' on PATH. Is 'node' installed?というダイアログが!

Node.js でサーバーサイド書く簡単なサンプルを真似して作ろうとしてデバッグ環境を整えてるときに発生
node -vってやってもちゃんと返却値あるし原因がわからない。。。
"一応" 解決したのでその方法を記載します。

環境

  • OS: macOS Catalina version 10.15.1
  • VSCode: 1.40.2
  • Node: v12.14.0 (installed by nodebrew)

TLDR

launch.json に以下の設定を追加

"runtimeExecutable": "/usr/local/var/nodebrew/current/bin/node" <- which node の結果をそのまま貼り付けた

レファレンスによると

runtimeExecutable - absolute path to the runtime executable to be used. Default is node. See section Launch configuration support for 'npm' and other tools.

runtimeExecutable: '実行ファイルの絶対パスを書く。デフォルトはnode。'
のような感じかな。。?

戦いの記録

launch.json はどうなってる?

program はちゃんと app.js となっていて問題ない感じ。。。

{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version":"0.2.0","configurations":[{"type":"node","request":"launch","name":"Launch Program","skipFiles":["<node_internals>/**"],"program":"${workspaceFolder}/app.js"}]}

実行

でも実行すると "Cannot find runtime 'node' on PATH. Is 'node' installed?" が出る、、、なぜ、、、?泣
Is 'node' installed? とおっしゃいますがちゃんと node はインストールしてるよ???

調べる

いくつかサイトを見てみたけどなんかしっくりこない、、、
その中で runtimeExecutable という単語が出てきてパス指定してるような雰囲気が、、!
レファレンス見て真似してなんとかデバッグできるようになりました。

まとめ

この方法でデバッグできるようにはなったけどなぜデフォルトだと node がないと言われてしまうのか謎、、、
nodebrew でやってるから??
でも $PATH にはちゃんと追加してるしなー
ということで動いたけどしっくりこない感じ。。。
正しい方法知ってる方がいらっしゃったら教えて欲しい、、ぜひ、、っ

React.jsとAWS(cognito)の連携

$
0
0

何をする?

reactコンポーネントからAWSのcognitoにユーザ登録を行い、サインインを行います。

cognitoについて

アプリやwebサービスの認証基盤です。認証ロジックだけAWSに託します。
サービス提供者はログイン情報を保持することなく認証結果のみを受け取ります。
また、cognitoを介して様々なAWSサービスが使えるようになります。
詳細は公式ドキュメントを参照ください。
公式ドキュメント

バージョン

  • node.js 10.15.3
  • react.js ^16.12.0
  • material-ui ^0.20.2
  • amazon-cognito-identity-js ^3.0.15

準備

awsへの連携にはamazon-cognito-identity-jsを使います。

 npm install --save amazon-cognito-identity-js

詳細はこちらを参照ください。
github公式ページ

ユーザプールの作成

userpool.png

アプリクライアント.png

ユーザプール > ユーザとグループ
にアカウント情報が蓄積されます。
プール ARNとプール IDとアプリクライアントIDはプログラムで使用します。
また、今回はサインイン時の二段階認証にEメールを利用します。

IDプールの作成

IDpool.png

IDプールの編集.png

ユーザプールへの登録時にIDプールにも情報を登録します。
これにより、登録ユーザはIDプールを介して他のAWSサービスを利用できるようになります。
IDプールのIDはプログラムで使用します。

これでcognitoを使用する準備ができました。
次にアプリケーションとcognitoを連携します。

該当のプログラム

サインイン画面

まずはreact側でユーザIDとパスワードとEメールアドレスを入力する画面を作成。

signin.js
importReactfrom'react';importPropTypesfrom'prop-types';import{withStyles,createMuiTheme,MuiThemeProvider}from'@material-ui/core/styles';importButtonfrom'@material-ui/core/Button';import{connect}from"react-redux"importFormControlfrom'@material-ui/core/FormControl';importInputfrom'@material-ui/core/Input';importInputLabelfrom'@material-ui/core/InputLabel';import{compose}from'redux'importGridfrom'@material-ui/core/Grid';importCognitoAuthfrom'aws'conststyles=theme=>({contents:{margin:'150px auto 0 ',maxWidth:'1260px',},formContents:{margin:'0 auto',maxWidth:'480px'},formControl:{display:'block',width:'100%'},btn:{marginTop:'20px',}});functiontypographyV1Theme(theme){returncreateMuiTheme({...theme,typography:{useNextVariants:false,},});}classSignInextendsReact.Component{constructor(props){super(props);this.state={password:'',user_id:'',email:'',errors:[],};}goError=async(e)=>{awaitthis.setState({errors:(e.message||JSON.stringify(e))})awaitconsole.log("エラー:"+this.state.errors)};goNext=async()=>{try{awaitthis.props.history.push({pathname:'/activate',state:{user_name:this.state.user_id,password:this.state.password,email:this.state.email,user_id:this.state.user_id}})}catch(e){this.goError(e)}};signInCognito=async()=>{awaitalert(this.state.user_id)awaitCognitoAuth.signIn(this.state.user_id,this.state.password,this.state.email).then(this.goNext).catch(this.goError)};render(){const{classes,dispatchAddValue}=this.props;return(<MuiThemeProvidertheme={typographyV1Theme}><divclassName={classes.contents}><p>お名前をメールアドレスを入力してください。</p>
</div>
<divclassName={classes.formContents}><Gridcontainerspacing={16}><Griditemxs={12}sm={12}><FormControlclassName={classes.formControl}><InputLabelhtmlFor="component-simple">ID</InputLabel>
<Inputid="component-simple"name="user_id"fullWidthdefaultValue={this.state.user_id}/>
</FormControl>
</Grid>
</Grid>
<Gridcontainerspacing={16}><Griditemxs={12}sm={12}><FormControlclassName={classes.formControl}><InputLabelhtmlFor="component-simple">パスワード</InputLabel>
<Inputid="component-simple" name="password"fullWidthdefaultValue={this.state.password}/>
</FormControl>
</Grid>
</Grid>
<Gridcontainerspacing={16}><Griditemxs={12}sm={12}><FormControlclassName={classes.formControl}><InputLabelhtmlFor="component-simple">メールアドレス</InputLabel>
<Inputid="component-simple"name="email"fullWidthdefaultValue={this.state.email}onChange={this.handleChange}/>
</FormControl>
</Grid>
</Grid>
<Gridcontainerspacing={16}><Griditemxs={12}sm={12}>{this.state.errors}</Grid>
</Grid>
<GridclassName={classes.btn}containerspacing={16}><Griditemxs={12}sm={12}><Buttonvariant="contained"color="secondary"fullWidthclassName={classes.fcButton}onClick={this.signInCognito}>次へ</Button>
</Grid>
</Grid>
</div>
</MuiThemeProvider>
);}}SignIn.propTypes={classes:PropTypes.object.isRequired,};exportdefaultcompose(withStyles(styles),connect(mapStateToProps,mapDispatchToProps))(SignIn)

画面はこんなイメージ
サインイン.png

認証

「次へ」ボタンを押すとCognitoAuth.signInが実行され、入力したアドレスに認証コードが届きます。

aws.js
importAWSfrom'aws-sdk'constAmazonCognitoIdentity=require('amazon-cognito-identity-js')constCognitoAuth={signIn:function(userName,password,email=null,phoneNumber=null){returnnewPromise(function(resolve,reject){varuserPoolData={UserPoolId:[ユーザプールID],ClientId:[ユーザプールクライアントID]}varuserPool=newAmazonCognitoIdentity.CognitoUserPool(userPoolData)varattributeList=[];if(email!==null){varattribute={Name:'email',Value:email};attributeList.push(newAmazonCognitoIdentity.CognitoUserAttribute(attribute));}if(phoneNumber!==null){varattribute={Name:'phone_number',Value:phoneNumber};attributeList.push(newAmazonCognitoIdentity.CognitoUserAttribute(attribute));}returnuserPool.signUp(userName,password,attributeList,null,function(err,result){if(err){reject(err)}resolve(result.user)})})},}exportdefaultCognitoAuth;

認証コード入力

届いた認証コードと、ユーザネームと、パスワードを引数にconfirmRegistrationを実行するだけで認証が通ります。そのままログイン処理を実行することでサインイン後は自動的にホーム画面などに遷移させることができます。

aws.js
importAWSfrom'aws-sdk'constAmazonCognitoIdentity=require('amazon-cognito-identity-js')constCognitoAuth={・・・confirmRegistration:function(userName,password,registrationCode){returnnewPromise(function(resolve,reject){varauthenticationData={Username:userName,Password:password}varauthenticationDetails=newAmazonCognitoIdentity.AuthenticationDetails(authenticationData)varuserPoolData={UserPoolId:[ユーザプールID],ClientId:[ユーザプールクライアントID]}varuserPool=newAmazonCognitoIdentity.CognitoUserPool(userPoolData)constuserData={Username:userName,Pool:userPool}varcognitoUser=newAmazonCognitoIdentity.CognitoUser(userData)cognitoUser.setAuthenticationFlowType('USER_PASSWORD_AUTH')cognitoUser.confirmRegistration(registrationCode,true,function(err,result){if(err){reject(err)}resolve(result)})})},}exportdefaultCognitoAuth;

ログイン

aws.js
importAWSfrom'aws-sdk'constAmazonCognitoIdentity=require('amazon-cognito-identity-js')constCognitoAuth={・・・doLogin:function(userName,password){returnnewPromise(function(resolve,reject){letauthenticationData={Username:userName,Password:password};letauthenticationDetails=newAmazonCognitoIdentity.AuthenticationDetails(authenticationData)letuserPoolData={UserPoolId:[ユーザプールID],ClientId:[ユーザプールクライアントID]};letuserPool=newAmazonCognitoIdentity.CognitoUserPool(userPoolData)constuserData={Username:userName,Pool:userPool};letcognitoUser=newAmazonCognitoIdentity.CognitoUser(userData)cognitoUser.setAuthenticationFlowType('USER_PASSWORD_AUTH')cognitoUser.authenticateUser(authenticationDetails,{onSuccess:function(result){AwsAuth.setJwtToken(result.getIdToken().getJwtToken())AWS.config.region=[リージョン]AWS.config.credentials=newAWS.CognitoIdentityCredentials({IdentityPoolId:[IDプールのID],Logins:{[[プールARN]]:result.getIdToken().getJwtToken()}});AWS.config.credentials.clearCachedId()AWS.config.credentials.refresh((error)=>{if(error){reject(error)}else{resolve(result.getIdToken().getJwtToken())}});},onFailure:function(err){console.log(err)reject(err)},mfaRequired:function(result){reject("mfaRequired")},newPasswordRequired(result){reject("newPasswordRequired")},customChallenge(result){reject("customChallenge")}})})}}

まとめ

cognitoを使うことでユーザのログイン情報をDBなどに保持する必要がなくなるのでサービスを提供する側にとっては大きなリスク回避になりますね。また、facebookやtwitterアカウントなどのリソースを利用した認証基盤も簡単に導入できます。

Viewing all 8892 articles
Browse latest View live