- 投稿日:2020-02-13T20:03:01+09:00
ElectronでcontextBridgeによる安全なIPC通信(受信編)
はじめに
Electronにおけるメインプロセスとレンダラープロセス間のやり取りに関して、セキュアなIPC通信にはcontextBridge1を使おう、という記事を前回書いたらそれなりに読んでもらえているみたいです。ありがとうございます。
その時の例として、レンダラープロセスからメインプロセスへの送信を扱いましたが、受信についてもリクエストがあったので紹介します。基本的にはStackOverFlow2からの引用です。
基本的にElectronにおけるメニュー操作はメインプロセスでハンドルすることになるので、それをレンダラープロセスへ送る際には、メインプロセスからチャンネル付きで信号を送信し、レンダラープロセスで受信時にチャンネルに従って処理を分ける、ということをするでしょう。これを目的としたcontextBridgeの利用法です。
前回からの改修点
まずは前回の記事の方法3までをお読みください。今回は前回の方法3からの改修点として次の様にしました。
- レンダラーからメインプロセスへの送信時にはチャンネルを設定して複数の信号の送信に対応した。
- メインプロセスから送信してレンダラープロセスで受信する部分では:
- アプリのメニュークリックで送信(メニュー操作はメインプロセスの範疇)
- レンダラーで受信したらHTMLに反映
方法4:レンダラーでの受信
メインプロセスのコードは
main.js
とします。preloadファイルはpreload.js
、レンダラープロセスで読み込むhtmlファイルはindex.html
とします。各ファイルの中身は次のようになります。/* main.js, case 4(extend: send and recv) */ "use strict"; const {electron,BrowserWindow,app,ipcMain,Menu} = require('electron'); let mainWindow = null; const CreateWindow = () => { mainWindow = new BrowserWindow({width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: __dirname + '/preload.js' } }); mainWindow.webContents.openDevTools(); mainWindow.on('closed', function() { mainWindow = null; }); /*menu creation*/ const template = [ { label: 'File', submenu: [{ label: 'Open', click: (menuItem, browserWindow, event) => { // メニュークリック時に実行される関数// //ここでファイルを開いたりする(今回は暫定)// const openedPath = "./hogehoge.txt"; const readData = "ファイルの中身だよ"; //ここまででファイルは読み込んだものとする// //この関数でIPC送信(main to renderer)// browserWindow.webContents.send( "openfile", //送信チャンネル名(自分で区別できるように)// { //送信したいデータ一覧// filePath: openedPath, dataText: readData } ); } }] }]; const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu); mainWindow.loadURL('file://' + __dirname + '/index.html'); } app.on('ready', CreateWindow); //IPC受信部// ipcMain.on("msg_render_to_main1", (event, arg) => { console.log(arg); //printing "good job" }); ipcMain.on("msg_render_to_main2", (event, arg) => { console.log("We are the", arg.teamName, "!"); //printing "We are the Victories !" });/* preload.js, case 4 (extend)*/ const { contextBridge, ipcRenderer} = require("electron"); contextBridge.exposeInMainWorld( "api", { send: (channel, data) => {//rendererからの送信用// ipcRenderer.send(channel, data); }, on: (channel, func) => { //rendererでの受信用, funcはコールバック関数// ipcRenderer.on(channel, (event, ...args) => func(...args)); } } );<!--index.html, case 4 (extend)--> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Test</title> </head> <body> <button id="button1">test1</button> <button id="button2">test2</button> <div id="previewF">受信ファイル名</div> <div id="previewD">受信データ</div> </body> <script type = "text/javascript"> //適当なプログラム const button1 = document.getElementById("button1"); //送信用(チャンネル名指定)// button1.addEventListener("click", (e)=>{ window.api.send("msg_render_to_main1", "god job"); }); //送信用(チャンネル名指定)// button2.addEventListener("click", (e)=>{ window.api.send("msg_render_to_main2", {teamName: "Victories"}); }); //受信部(チャンネル名指定)// window.api.on("openfile", (arg)=>{ document.getElementById("previewF").textContent = arg.filePath; document.getElementById("previewD").textContent = arg.dataText; }); </script> </html>まず、レンダラープロセスからメインプロセスへ送信する部分ですが、ボタンを二つ配置し、チャンネル名を変えて二種類の信号が送れるようになっています。メインプロセスでは
ipsMain.on(チャンネル名, コールバック関数)
でチャンネル名を指定することで、処理を分けて行えるようになります。次に、本目的のレンダラープロセスでの受信ですが、
preload.js
でのon
の部分の記述がポイントです。コールバック関数名をfunc
としておいて、ipcRenderer.on()
の中ではスプレッド構文...args
を使っています。これにより、メインプロセスから送られてきた引数のうち、event
だけを取り除いてコールバック関数へ渡しています。コールバック関数はindex.html
内のwindow.api.on("openfile",(arg)=>{ ... })
にて記述できるので、実質的にipcRenderer.on()
を直接書いていた時代と同様に利用できます。注意点
contextBridgeはとっても良さそうなAPIですが、Electronのドキュメント3には次のように書かれています。
"The
contextBridge
API has been published to Electron's master branch, but has not yet been included in an Electron release."一応、私の環境のversion7.1.9では使えていますが、いつから使えるようになったのかはちょっと不明なので、気を付けてください。
感想
コールバック関数という表現が合っているのか不安ですが、Javascriptライト勢なのでご勘弁くだされ。
- 投稿日:2020-02-13T16:37:14+09:00
【MongoDB】APIログ取るのに手軽で最高だった件 (+intellijだとさらに手軽)
はじめに
気にはなっていたけど、なかなか触れる機会が無かった。。
そんな、同じクラスのあの子のような存在、それがmongoDBでした。
実際、使ってみると手軽でとても使いやすい。こちらの記事では、簡単にインストールから導入までをまとめてみました。
MongoDBを使った開発内容
趣味の個人開発でMongoDBを利用しました。
[APIを利用したbitcoin自動売買システム]
- bitcoin値取得にCryptWatchAPIを使用
- bitcoin売買にBitflyerAPIを使用
- 開発言語:Node.js
- 開発環境:macOS Catalina
- デプロイ環境: AWS:EC2:ubuntu18.04LTS
MongoDBはbitcoin売買時の値段と、その売買判断に使われた値のログを取りたくて使用しました。
mongoDBとは
誤解を恐れずに極端に言うと、
データをJSON形式でレコードできるデータベースです
すいません! ここでは、わかりやすさ優先しましたm(_ _)m
(玄人の方々、マサカリ投げないでください。)他にも、
スケーラビリティ(拡張)しやすい、
スキーマレス(事前定義不要)である。 などなど、、ありますが、
詳しく知りたい方は以下のページをおすすめします。
詳しいオススメページ1
詳しいオススメページ2APIをレコードするのに最適!
mongoの特徴
- スキーマレスで、
- JSON形式
これが、APIを記録するのにすごく相性がいいです。
つまり、
APIからレスポンスされたJSONをそのままインサートすることができる!
さらに、
毎回APIの構造が変わってもそのままインサート可能!!
すごい楽ですね、
スキーマレスなので難しく考えず、ひとまず突っ込んで置くことができます。これが、RDBMSだとどうなるか?
- スキーマを細かく定義した上で、例外処理を施し構造が変わるものは省いてインサートする。
- string,textとしてまるごとインサート
どちらも大変です。。
受け取る可能性のあるJSON内容を想定して、スキーマを細かく定義した上で、
処理は、レスポンスされたJSONをカラムに収まるように全部分解したり、例外処理を書いたり。。
2つ目は、まる投げでインサートすればいいのですが、columnがTEXT形式でも文字数制限あります。
mysqlだと最大長が65,535(216 − 1)文字になります。データを利用するときはまた、JSON形式に戻して。。
うぅ、吐きそうです(;´Д`)
しかも、ノード同士の構造を捨てちゃってます。「今は何に使うかわかんないけどー、
あとで、面白いことに使いたいから、とりあえずサクッとデータ残しておこう♪」
こんなノリには、ぜひMongoDBです。私のbitcoin売買システムも、
将来、趣味で分析したり、機械学習で回せたらオモロイだろうなーってノリだけです。
使えそうな値と、APIをまるごと、とりあえずインサートおく。
APIの構造が多少変わっても気にしない。まずはその手軽さを触ってみることをオススメします。
それでは、簡単に導入方法をまとめます。インストール
macOS
brewを使って、簡単にインストールできます。
brew install mongodb #自動起動に設定 brew services start mongodbubuntu18.04LTS
ubuntuはやや面倒です。
aptの管理ライブラリが最新のMongDBとなっていないため、
Mongoの公式から、パッケージを展開してインストールする必要があります。#パッケージ管理システムに公開鍵を登録 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4 #ダウンロード用のリストファイルを作成 echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list #パッケージ管理システムのデータベースをリロード sudo apt-get update #最新の安定版をインストール sudo apt-get install -y mongodb-org #MongoDBを自動起動にする sudo systemctl enable mongod #MongoDBを起動 sudo service mongod startraspbrerryPi
ラズパイにMongDBの最新バージョンはインストールできません。
正しくは、
ラズパイの公式OS:RasbianOSには、MongDB version2.4.14以上はインストールできません。上記のMongoDBバージョンが、64bit対応のみとなっていますが、
RasbianOSが、32bitとなっているためです。実は、ラズパイ自体は64bitのため
OSに引きづられて最新のMongoDBが利用できなくなっています。
ですので、OSを入れ替えたら使用できるかもしれません。
詳しくはこちらmongo2.4以下だと、npmのmongoライブラリも対応していないため、
ラズパイでmongoDBを扱うのは特別な理由が無い限りあまりおすすめしないです。実装
データベースの設定
ターミナルからmongoにデータベースを作成して、利用できるようにします。
# MongoDBに入る mongo # データベース切り替え、作成 use db_name #データベースの確認 show dbs #使用しているデータベースを確認 db #コレクションの作成 db.createCollection("collect_name") #コレクションを確認 show collections #コレクションにドキュメントをインサート db.collect_name.insertOne({ name: "hoge", age: 88}) #コレクション内のドキュメントを確認 db.collect_name.find() # MongoDBから抜ける exitちょっと説明
- use db_name: データベースは無ければ、自動で作成されます。
- collection: mongoではtableでは無く、collectionと言います。
- document:mongoでは1つ1つのデータ(record)のことをdocumentと言います。
コード実装
Node.jsで実装しました。
以下はベースの実装例です。const MongoClient = require('mongodb').MongoClient; const options = { useUnifiedTopology : true, useNewUrlParser : true }; const url = 'mongodb://localhost:27017'; const dbName = 'db_name'; //即時関数と、asyncを定義 (async function () { let client; try { client = await MongoClient.connect( url, options, ); //DB取得 const db = client.db(dbName); //DBを操作 await insertDoc(db); await findDoc(db); } catch (err) { //接続失敗 console.log(err); } //接続を切る client.close(); })(); //Insert function insertDoc(db) { const collection = db.collection('collect_name'); collection.insertMany( [{ name: 'hoge', age: 88 }, { name: 'fuga', age: 14 }], (err, result) => { console.log('Success inserted'); }, ); } //Find function findDoc(db) { const collection = db.collection('collect_name'); collection.find({}).toArray((err, docs) => { //検索内容をコンソール出力 console.log(docs); }); }データ確認
bitcoinの売買データをmongoで取得してみました。
標準のデータ確認
#terminalからの標準のデータ確認 { "_id" : ObjectId("5df9c2ba73160d276ad2e3ad"), "flag" : "buy", "label" : "買い注文", "created_at" : ISODate("2019-12-18T06:10:02.072Z"), "strTime" : "2019/12/18 15:10:02", "price" : 737491, "shortMA" : 737293.825779211, "longMA" : 736227.9333333333, "countHigh" : 4, "records" : [ 735413, 735522, 735314, 735516, 735691, 735913, 736276, 736316, 736366, 736788, 736586, 736472, 735565, 735245, 735327, 735259, 735677, 735745, 736058, 736238, 736448, 736713, 736388, 736426, 736954, 737198, 736975, 737479, 737479, 737491 ] }一部説明します
- _id: mongoから自動で振られるid
- created_at: ISODATE形式、datetime形式でinsertすることができます。
- shortMA: 過去5回分のpriceの移動平均金額を算出
- longMA: 過去30回分のpriceの移動平均金額を算出
- records: 過去のpriceリスト。リスト形式でそのままinsertしています。
データの形式がlistでも、さらにツリーが入れ子状態になっても、insertすることができます。
Intellij(jetBrains)を利用
CLIからだと、RDBMSよりも一覧性なくて見ずらいですね。データの閲覧はIDEや、ソフトをオススメします。
私は、IntelliJを使っています。
一度だけ接続設定をすれば、それ以後、ダブルクリックだけで、DBに接続してこのようにデータ閲覧できます。
ターミナルでコマンドを打つよりも、データ確認が手軽で早くて、見やすいです。本来は上記のようなJSON形式のnodeツリーが、テーブル表示で一覧できます。
クリックひとつでソートもできますし、絞り込みも簡単です。
ツリー形式での閲覧もできます。
transposed table 日本語で何形式と言うのでしょう?こんな閲覧方法もできます。
強いて不満を言えば、
table viewのとき、列の順番がバラバラとなるのが気になります。
(そもそもMongoDBにはDDLで読み取れるようなculumnの順番という概念がないのでしょうが無いです。)おわりに
MongDBは、お手軽で使い勝手がよいDBです。
APIデータを、今の時点ではどんな風に活用するか厳密に決めていないけど、
ひとまず導入しておいて後で考えるには、MongoDBは良い選択肢だと思います♪最後までお読みいただきありがとうございました。
- 投稿日:2020-02-13T15:08:42+09:00
VPSで複数のNodeJSアプリをHTTPS化してホストする
VPSで複数のサービスのAPIサーバーを運用するための手順です。
概要
- Digital Ocean の Ubuntu イメージ(5USD/month)
- サブドメインに各アプリを紐づける
- Let’s Encrypt で SSL 化
サーバーのセットアップ
Digital Ocean Droplets のセットアップ
以下の設定でDropletsを作ります。
※ 他社のVPSサービスを利用する場合は、ドメインの@
www
app1
app2
をサーバーに向けてSSH接続できる状態までやってこの項を飛ばしてください。
- Image: Ubuntu
- Plan: Standard / 5USD
- Region: シンガポール
- Authentication: SSH keys
ドメインの管理画面から使うドメインの
www
@
app1
app2
を上記で作ったサーバーに向けます。# SSH 接続 できることを確認 $ ssh root@yourdomain.comメモリのスワップ領域を作る
月額5USDなどの安いVPSを利用しているとメモリ不足になることがあります。事前に十分な仮想メモリを設定しておきます。
# スワップ領域が未指定であることを確認 $ free # スワップディレクトリの作成 $ sudo mkdir /var/swap/ # スワップファイルを作成 $ sudo dd if=/dev/zero of=/var/swap/swap0 bs=1M count=1024 $ sudo chmod 600 /var/swap/swap0 # 上記のファイルをスワップ領域として割り当てる $ sudo mkswap /var/swap/swap0 $ sudo swapon /var/swap/swap0 # 再起動で再設定できるようにする # fstab に `/var/swap/swap0 swap swap defaults 0 0` を追記します。 $ sudo vi /etc/fstab # 動作確認 $ freeログインユーザーを作る
Root ユーザーでログインし続けるのはセキュリティ的によくないので新しいユーザーを追加します。
# ユーザーの追加 $ adduser taro # 作ったユーザーにsudo権限を与える $ usermod -aG sudo taro # ssh 接続できるようにする $ rsync --archive --chown=taro:taro ~/.ssh /home/taroファイアウォールの設定
ファイアウォールを有効化してSSH接続のみを許可するようにします。
# OpenSSHを許可 $ ufw allow OpenSSH # 有効化 $ ufw enable # 動作確認 $ ufw statusVSCODEの設定
VSCODEからリモートサーバーのファイルを編集できるようにします。
- extentions
remote ssh
をインストールします。- Remote-SSH: connect to host…
- Add new Host
- ssh root@yourdomain.com
NGINX で HTTPS を使う
Nginx のインストール
新しく作ったユーザーで Nginx をインストールします。
# インストール $ sudo apt update $ sudo apt install nginx # firewallにnginxを許可します $ sudo ufw allow 'Nginx Full' # 動作確認 $ systemctl status nginxyourdomain.com にブラウザからアクセスしてnginxのデフォルト画面が表示されることを確認します。
Nginx の設定ファイル
/etc/nginx
にNginxの設定ファイル一式があります。その中から/etc/nginx/conf.d/home.conf
を作って編集していきます。/etc/nginx/conf.d/home.confserver { server_name [yourdomain].com www.[yourdomain].com; location / { root /var/www; index index.html; } } server { server_name app1.[yourdomain].com; location / { root /var/app1; index index.html; } }設定を読み込みんでnginxを再起動します。
# テスト $ service nginx configtest # 再起動 $ systemctl reload nginx
var/www/index.html
,var/app1/index.html
を適当に作ってブラウザでURLにアクセスすると各index.htmlが表示されるはずです。Let’s EncryptでHTTPSの設定
# certbot のインストール $ sudo add-apt-repository ppa:certbot/certbot $ sudo apt install python-certbot-nginx # 証明書をリクエスト $ sudo certbot --nginx -d www.[yourdomain].com -d [yourdomain].com -d app1.[yourdomain].com # 証明書の再発行設定 $ sudo certbot renew --dry-runhttps でアクセスできるようになります。
NodeJSアプリケーションをNginxでリバースproxyする
nodeJSのインストール
# インストール $ curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash - $ sudo apt-get install -y nodejs $ sudo apt-get install build-essential # 動作確認 $ node -v $ npm -vデモアプリを動かしてみる
$ mkdir ~/app1 $ cd ~/app1 $ npm init $ npm install --save expressVS code で上記で作ったディレクトリにアクセスしてデモアプリを作ります。
[フォルダを作る] > [/home/taro/app1/]
index.jsconst express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => res.send('Hello World From NodeJS!')); app.listen(port, () => console.log(`Example app listening on port ${port}!`));アプリを起動して動作を確認します。
# サーバーに接続して実行 $ node index.js # 別のターミナルを起動して動作を確認 $ curl http://localhost:3000注意: この段階では、yourdomain.com:3000 にアクセスして動作確認できません。
PM2をインストールしてアプリを永続化します
$ sudo npm install pm2@latest -g $ pm2 start index.js # 動作確認 $ curl http://localhost:3000Nginx Reverse Proxy server の設定
Nginx Reverse Proxy server
を設定してhttp://localhost:3000
を外にだします。/etc/nginx/conf.d/home.conf
を編集します。/etc/nginx/conf.d/home.conf... server { server_name app1.[yourdomain].com; location / { proxy_pass http://localhost:3000; } listen 443 ssl; # managed by Certbot ...
$ sudo systemctl restart nginx
でnginx
を再起動します。
app1.[yourdomain].com
にブラウザでアクセスするとのHello World From NodeJS!
が表示されるはずです。別のドメインを追加してHTTPS化する
home.conf
に新しいドメインの設定を追加します。/etc/nginx/conf.d/home.confserver { server_name app2.[yourdomain].com; location / { root /var/app2; index index.html; } }Let’s EncryptでHTTPS証明書を発行します。
# nginxの再起動 $ systemctl reload nginx # 証明書のリクエスト $ sudo certbot --nginx -d app2.[yourdomain].com # 証明書の再発行設定 $ sudo certbot renew --dry-runhttps で app2.yourdomain.com にアクセスできます。
その他
- Digital Ocean で Droplets の破壊作成を繰り返すとき、DestroyではなくRebuildを選択するとIPが引き継がれるので便利です。
参考
- How To Install Nginx on Ubuntu 18.04 | DigitalOcean
- Initial Server Setup with Ubuntu 18.04
- How To Set Up a Node.js Application for Production on Ubuntu 18.04
- 物理メモリが足りない時はスワップメモリを使おう。(Ubuntu) | tyablog.net
- 2019-09-07 メモ nginx - Qiita
- 秩序と情報とブロッコリー: nginxのインストールからマルチドメインの設定まで(@CentOS)
- 【nginx】サブドメインの設定方法 - IT技術総合Wiki | CWiki
- 投稿日:2020-02-13T15:08:42+09:00
[2020] VPSで複数のNodeJSアプリをHTTPS化してホストする
VPSで複数のサービスのAPIサーバーを運用するための手順です。
概要
- Digital Ocean の Ubuntu イメージ(5USD/month)
- サブドメインに各アプリを紐づける
- Let’s Encrypt で SSL 化
サーバーのセットアップ
Digital Ocean Droplets のセットアップ
以下の設定でDropletsを作ります。
※ 他社のVPSサービスを利用する場合は、ドメインの@
www
app1
app2
をサーバーに向けてSSH接続できる状態までやってこの項を飛ばしてください。
- Image: Ubuntu
- Plan: Standard / 5USD
- Region: シンガポール
- Authentication: SSH keys
ドメインの管理画面から使うドメインの
www
@
app1
app2
を上記で作ったサーバーに向けます。# SSH 接続 できることを確認 $ ssh root@yourdomain.comメモリのスワップ領域を作る
月額5USDなどの安いVPSを利用しているとメモリ不足になることがあります。事前に十分な仮想メモリを設定しておきます。
# スワップ領域が未指定であることを確認 $ free # スワップディレクトリの作成 $ sudo mkdir /var/swap/ # スワップファイルを作成 $ sudo dd if=/dev/zero of=/var/swap/swap0 bs=1M count=1024 $ sudo chmod 600 /var/swap/swap0 # 上記のファイルをスワップ領域として割り当てる $ sudo mkswap /var/swap/swap0 $ sudo swapon /var/swap/swap0 # 再起動で再設定できるようにする # fstab に `/var/swap/swap0 swap swap defaults 0 0` を追記します。 $ sudo vi /etc/fstab # 動作確認 $ freeログインユーザーを作る
Root ユーザーでログインし続けるのはセキュリティ的によくないので新しいユーザーを追加します。
# ユーザーの追加 $ adduser taro # 作ったユーザーにsudo権限を与える $ usermod -aG sudo taro # ssh 接続できるようにする $ rsync --archive --chown=taro:taro ~/.ssh /home/taroファイアウォールの設定
ファイアウォールを有効化してSSH接続のみを許可するようにします。
# OpenSSHを許可 $ ufw allow OpenSSH # 有効化 $ ufw enable # 動作確認 $ ufw statusVSCODEの設定
VSCODEからリモートサーバーのファイルを編集できるようにします。
- extentions
remote ssh
をインストールします。- Remote-SSH: connect to host…
- Add new Host
- ssh root@yourdomain.com
NGINX で HTTPS を使う
Nginx のインストール
新しく作ったユーザーで Nginx をインストールします。
# インストール $ sudo apt update $ sudo apt install nginx # firewallにnginxを許可します $ sudo ufw allow 'Nginx Full' # 動作確認 $ systemctl status nginxyourdomain.com にブラウザからアクセスしてnginxのデフォルト画面が表示されることを確認します。
Nginx の設定ファイル
/etc/nginx
にNginxの設定ファイル一式があります。その中から/etc/nginx/conf.d/home.conf
を作って編集していきます。/etc/nginx/conf.d/home.confserver { server_name [yourdomain].com www.[yourdomain].com; location / { root /var/www; index index.html; } } server { server_name app1.[yourdomain].com; location / { root /var/app1; index index.html; } }設定を読み込みんでnginxを再起動します。
# テスト $ service nginx configtest # 再起動 $ systemctl reload nginx
var/www/index.html
,var/app1/index.html
を適当に作ってブラウザでURLにアクセスすると各index.htmlが表示されるはずです。Let’s EncryptでHTTPSの設定
# certbot のインストール $ sudo add-apt-repository ppa:certbot/certbot $ sudo apt install python-certbot-nginx # 証明書をリクエスト $ sudo certbot --nginx -d www.[yourdomain].com -d [yourdomain].com -d app1.[yourdomain].com # 証明書の再発行設定 $ sudo certbot renew --dry-runhttps でアクセスできるようになります。
NodeJSアプリケーションをNginxでリバースproxyする
nodeJSのインストール
# インストール $ curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash - $ sudo apt-get install -y nodejs $ sudo apt-get install build-essential # 動作確認 $ node -v $ npm -vデモアプリを動かしてみる
$ mkdir ~/app1 $ cd ~/app1 $ npm init $ npm install --save expressVS code で上記で作ったディレクトリにアクセスしてデモアプリを作ります。
[フォルダを作る] > [/home/taro/app1/]
index.jsconst express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => res.send('Hello World From NodeJS!')); app.listen(port, () => console.log(`Example app listening on port ${port}!`));アプリを起動して動作を確認します。
# サーバーに接続して実行 $ node index.js # 別のターミナルを起動して動作を確認 $ curl http://localhost:3000注意: この段階では、yourdomain.com:3000 にアクセスして動作確認できません。
PM2をインストールしてアプリを永続化します
$ sudo npm install pm2@latest -g $ pm2 start index.js # 動作確認 $ curl http://localhost:3000Nginx Reverse Proxy server の設定
Nginx Reverse Proxy server
を設定してhttp://localhost:3000
を外にだします。/etc/nginx/conf.d/home.conf
を編集します。/etc/nginx/conf.d/home.conf... server { server_name app1.[yourdomain].com; location / { proxy_pass http://localhost:3000; } listen 443 ssl; # managed by Certbot ...
$ sudo systemctl restart nginx
でnginx
を再起動します。
app1.[yourdomain].com
にブラウザでアクセスするとのHello World From NodeJS!
が表示されるはずです。別のドメインを追加してHTTPS化する
home.conf
に新しいドメインの設定を追加します。/etc/nginx/conf.d/home.confserver { server_name app2.[yourdomain].com; location / { root /var/app2; index index.html; } }Let’s EncryptでHTTPS証明書を発行します。
# nginxの再起動 $ systemctl reload nginx # 証明書のリクエスト $ sudo certbot --nginx -d app2.[yourdomain].com # 証明書の再発行設定 $ sudo certbot renew --dry-runhttps で app2.yourdomain.com にアクセスできます。
その他
- Digital Ocean で Droplets の破壊作成を繰り返すとき、DestroyではなくRebuildを選択するとIPが引き継がれるので便利です。
参考
- How To Install Nginx on Ubuntu 18.04 | DigitalOcean
- Initial Server Setup with Ubuntu 18.04
- How To Set Up a Node.js Application for Production on Ubuntu 18.04
- 物理メモリが足りない時はスワップメモリを使おう。(Ubuntu) | tyablog.net
- 2019-09-07 メモ nginx - Qiita
- 秩序と情報とブロッコリー: nginxのインストールからマルチドメインの設定まで(@CentOS)
- 【nginx】サブドメインの設定方法 - IT技術総合Wiki | CWiki
- 投稿日:2020-02-13T13:53:11+09:00
Node.js と Babel で ES6
なんか、いつも、Node.js で、ちょっとコード書いて試したり、勉強がてらコード書いたりするときに、 ES6なコード書きたい時どうすんだっけ? と悩んでしまうので、ここにメモしておきます。
実際に確認した時のそれぞれのバージョンは以下になります。
$ node --version v12.14.1 $ npm --version 6.13.4 $ npx --version 6.13.4あと、このメモ作成時にインストールされる Babel 関連パッケージのバージョンは、以下の通り。
$ grep babel package.json "@babel/cli": "^7.8.4", "@babel/core": "^7.8.4", "@babel/node": "^7.8.4", "@babel/preset-env": "^7.8.4"作業用のディレクトリを作る
mkdir worknpm init を実行
npm init -ybabel 関連をインストール
npm install --save-dev @babel/core @babel/preset-env @babel/cli @babel/node.babelrc を追加
{ "presets": ["@babel/preset-env"] }スクリプトを実行するには
index.js を実行するには以下のようにする。
npx babel-node index.js最後に自分に向かって一言
これ、シェルスクリプトにしとけば良いよね? > 自分
- 投稿日:2020-02-13T09:49:20+09:00
Nowでプロキシを建ててCORSエラーを爆速解決
前置き
nuxt generate
で作った静的サイトをGitHub Pagesにホスティングし,自分のはてなブックマークのRSSをAxiosでGETして表示させようとしていました.何も考えずにブラウザで開くとこんなエラーが出てうまくいきません.
Access to XMLHttpRequest at 'http://api.example.com' from origin 'http://localhost.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.いわゆる,オリジン間リソース共有(CORS)における同一オリジンポリシー違反です.このエラーはブラウザにおいて,現在アクセスしてるサイトと異なるオリジンにあるリソースに対してリクエストを行うときに起きます.
今回の場合,CORS解決に必要なヘッダー情報がはてな側からのレスポンスに含まれていないことが原因で,ブラウザによって通信をブロックされてしまいました.CORS解決に必要なヘッダーというのは
Access-Control-Allow-Origin: *
のようなフィールドです.参考: https://qiita.com/umechiki/items/82dd43cd1465de5f5afe
ちなみに,Nuxtでよく紹介されている@nuxtjs/proxyを使った回避方法はフロントエンド側だけで解決できますが,こいつは
nuxt generate
では機能しません.まさに,こちらの方と同じ状況になりました.以上のことから,代理でRSSを取得するプロキシを建てて,適切なヘッダを付与してレスポンスを返してくれるようにすれば,CORSを解決することができます.
この記事では,Zeit社のNowを使ってCORSエラーを簡単に解決できたのでメモします.Now
NowはZeit社のPaaSで,シンプルを極めたようなデプロイ方法が特徴です.
Zeitのアカウントを作り,デプロイ対象のディレクトリで$ npm i -g now $ now login $ now deployと実行していけば,ほぼ3ステップでデプロイできてしまいます.
Serverless Functionsを使うには
api
というディレクトリを作り,その中にtarget.js
を置いておくとデプロイ時に自動的にビルドされ,https://<project-name>.<username>.now.sh/api/target
でアクセスできるようになります.参考: https://zeit.co/docs/v2/serverless-functions/introduction
代わりにRSSを取って来てもらう
api
下に置くコードは,AxiosでGETしたはてなブックマークのレスポンスBodyにCORS解決に必要なヘッダを付与してリクエスト元に返すようにすればOKです.api/target.jsconst axios = require('axios') module.exports = async (req, res) => { await axios.get('https://b.hatena.ne.jp/<username>/bookmark.rss?of=1') .then((hatenaRes) => { res.setHeader('content-type', hatenaRes.headers['content-type']) res.setHeader('Access-Control-Allow-Origin', 'https://<username>.github.io') res.status(200).send(hatenaRes.data) }).catch((e) => { console.log(e) res.status(500).send('Internal Server Error.') }) }全て許す場合は
res.setHeader('Access-Control-Allow-Origin', '*')
を設定してください.これははてなのRSSを取るための小さな構成例ですが,
req.headers
やres.setHeader()
を使うことでより細かな制御を行うことができます.Access-Control-Allow-Origin
の設定だけでは解決できない場合もあるので,いろいろ実験してみてください.
- 投稿日:2020-02-13T06:20:46+09:00
NestJS CLIで初心者でも簡単にNode.js REST APIが作れる!
Node.jsなんてほとんど使ったことがないのに、頑張ってNestJSでCLIを使ってREST APIを作りました。
あまりドキュメントなかったことに加えて私のスキル不足で半日ほどかかってしまいましたが慣れれば30分もかからないです。環境
Ubuntu18.04.01 LTSで動かしました。
NEST CLIのnest info
でバージョン情報を見ます。$ nest info _ _ _ ___ _____ _____ _ _____ | \ | | | | |_ |/ ___|/ __ \| | |_ _| | \| | ___ ___ | |_ | |\ `--. | / \/| | | | | . ` | / _ \/ __|| __| | | `--. \| | | | | | | |\ || __/\__ \| |_ /\__/ //\__/ /| \__/\| |_____| |_ \_| \_/ \___||___/ \__|\____/ \____/ \____/\_____/\___/ [System Information] OS Version : Linux 4.15 NodeJS Version : v12.14.1 NPM Version : 6.13.4 [Nest CLI] Nest CLI Version : 6.14.2 [Nest Platform Information] platform-express version : 6.10.14 common version : 6.10.14 core version : 6.10.14手順
NestJS 入門を参考にしました。作ったプログラムはGitHubに置いてあります。
1. プロジェクト作成
nest new <プロジェクト名>
でプロジェクトを作ります。$ nest new nest-test ⚡ We will scaffold your app in a few seconds.. CREATE /nest-test/.eslintrc.js (599 bytes) CREATE /nest-test/.prettierrc (51 bytes) CREATE /nest-test/README.md (3370 bytes) CREATE /nest-test/nest-cli.json (64 bytes) CREATE /nest-test/package.json (1877 bytes) CREATE /nest-test/tsconfig.build.json (97 bytes) CREATE /nest-test/tsconfig.json (336 bytes) CREATE /nest-test/src/app.controller.spec.ts (617 bytes) CREATE /nest-test/src/app.controller.ts (274 bytes) CREATE /nest-test/src/app.module.ts (249 bytes) CREATE /nest-test/src/app.service.ts (142 bytes) CREATE /nest-test/src/main.ts (208 bytes) CREATE /nest-test/test/app.e2e-spec.ts (630 bytes) CREATE /nest-test/test/jest-e2e.json (183 bytes) ? Which package manager would you ❤️ to use? npm ✔ Installation in progress... ☕ ? Successfully created project nest-test ? Get started with the following commands: $ cd nest-test $ npm run start2. プロジェクト作成確認
プロジェクト作成時の指示に従い、nodeを起動します。
$ cd nest-test $ npm run start起動したのでcurlで確かめてみます。"Hello World!"を取得できました。もちろんブラウザでアクセスしても同じ結果です。
$ curl http://localhost:3000 Hello World!
3. Module作成
CLIから
nest generate module <モジュール名>
でModuleを生成。今回はModuleなどをすべてtest
で統一しています。nest generate module test CREATE /src/test/test.module.ts (81 bytes) UPDATE /src/app.module.ts (308 bytes)4. Interface作成
CLIから
nest generate interface <インターフェース名>
でInterfaceを生成。インタフェースはフォルダ付きで"test/test"にしています。他と違うので気持ち悪いのですが、こんなものなのでしょうか。nest generate interface test/test CREATE /src/test/test.interface.ts (25 bytes)生成された
test.interface.ts
を修正します。Interfaceに型を定義します。test.interface.tsexport interface Test { id: string; name: string; }5. Service作成
CLIから
nest generate service <サービス名>
でServiceを生成。$ nest generate service test CREATE /src/test/test.service.spec.ts (446 bytes) CREATE /src/test/test.service.ts (88 bytes) UPDATE /src/test/test.module.ts (155 bytes)手で
test.service.ts
を修正します。複数のTestを出力する関数listTest
とidを指定して単一のTestを返す関数getTest
を定義します。test.service.tsimport { Injectable } from '@nestjs/common'; import { Test } from './test.interface'; @Injectable() export class TestService { private readonly test: Test[] = [{id: 'a', name: 'Alex'}, {id: 'b', name: 'Bob'}, {id: 'c', name: 'Cathy'}]; listTest(): Test[] { return this.test; } getTest(id: string): Test { return this.test.find(value => value.id === id); } }6. Controller作成
CLIから
nest generate controller <コントローラー名>
でControllerを生成。$ nest generate controller test CREATE /src/test/test.controller.spec.ts (479 bytes) CREATE /src/test/test.controller.ts (97 bytes) UPDATE /src/test/test.module.ts (240 bytes)
test.controller.ts
を修正。先ほどサービス内で定義した関数を呼び出します。
@Controller('test')
とすることでパスtest
にアクセスしたときに動作するようにしています。@Get()
としたことでGETメソッドでアクセスしたときに動作します。@Get(':id')
は、パスtest
の配下に指定したidを受け取ります。test.controller.tsimport { Controller, Get, Param } from '@nestjs/common'; import { TestService } from './test.service'; import { Test } from './test.interface'; @Controller('test') export class TestController { constructor(private readonly testService: TestService) {} @Get() listUsers(): Test[] { return this.testService.listTest(); } @Get(':id') getTest(@Param('id') id: string): Test { return this.testService.getTest(id); } }7. 起動と確認
テストのためにnodeを起動。
nest startcurlで結果を確認します。無事、リスポンスを取得できました。
$ curl http://localhost:3000 Hello World $ curl http://localhost:3000/test [{"id":"a","name":"Alex"},{"id":"b","name":"Bob"},{"id":"c","name":"Cathy"}] $ curl http://localhost:3000/test/a {"id":"a","name":"Alex"}8. ビルド
最後に
nest build
でdist
フォルダ配下にコードをビルドします。nest build
- 投稿日:2020-02-13T01:18:31+09:00
GoogleのCloud Text-to-Speechを使ってDiscordの読み上げbotをサクっと作った
Discordのメッセージ読み上げbot
Discordのボイスチャットで、特定のチャンネル内のメッセージを自動で読み上げてくれるbotを作りました。
弊ディスコで導入されている読み上げbot pic.twitter.com/VvdzYlmqEz
— 古都こと (@kfurumiya) February 12, 2020Discordの読み上げbotとしては喋太郎という有名なものが既に存在しますが、自分で作ってみたくなったので作りました。
GitHub
https://github.com/kotofurumiya/helmholtz
作った背景
作ってみたくなったからです。以上。
というのも味気ないのでちゃんとした話をします。
Discordのボイスチャットは便利ですが、どうしても「今の時間帯は声出せない」とか「飯食ってる」とかでマイクをミュートにせざるをえない人が発生します。あるいは単純に「聞き専」な人も。
そういった人たちとコミュニケーションをとろうとするとなかなか難しく、「聞き専」用のテキストチャンネルを作り、そこにメッセージを書き込んでもらうことでなんとか双方向のやりとりを成り立たせていました。しかし会話に熱中にしていると聞き専チャンネルへの書き込みにどうしても気づけなかったり、ゲームの対戦中なんかだとメッセージが来たのがわかっていてもチラ見するのも難しかったりします。
そんな中で「聞き専チャンネルに書き込まれたメッセージを読み上げるbotがあればいいんじゃね?」と思いついたのがきっかけです。テキスト読み上げがあればメッセージが来たのに気がつくし、チャット欄に目線を動かさなくても何を言っているのか把握しやすくなります。読み上げbotさえあれば全部解決するんだろうなーと思い作り始めました。
名前の「ヘルムホルツ」はグラブルの武器から取りました。なんか音出しそうな感じの武器の名前ないかな〜と探してたらヘルムホルツが目についたので採用しました。
なお喋太郎のことを知ったのは作り終わってからです。まあいい勉強になったので良しとします。
機能
基本的な機能としてはテキスト読み上げしかありません。あとは自動入室・自動退室周りの機能ぐらいです。
- マイクミュートの人が発言するとそのボイスチャットチャンネルに自動参加
- 指定された特定のテキストチャンネルのみ対応
- マイクミュートの人の発言を自動で読み上げてくれる
- ボイスチャットに誰もいなくなったら自動退室
思想としては「ユーザは一切の操作不要。勝手に動く」を中心としています。
技術
使った技術自体はシンプルです。
- Node.js
- discord.js https://discord.js.org/
- @google-cloud/text-to-speech https://www.npmjs.com/package/@google-cloud/text-to-speech
- Google Cloud Platform(GCP)
- Google Compute Engine(GCE) https://cloud.google.com/compute/?hl=ja
- Container Registory https://cloud.google.com/container-registry/?hl=ja
- Cloud Text-to-Speech https://cloud.google.com/text-to-speech?hl=ja
- Docker
私にしては珍しくTypeScriptを採用せず生のJavaScriptを115行ゴリゴリ書いて動かしてます。中身もだいぶ雑です。
Discordの制御に関してはdiscord.jsという便利なライブラリがあったので使わせていただきました。テキスト読み上げ音声はCloud Text-to-Speechを使用しています。デプロイはDockerでコンテナ化してContainer Registoryにpushし、GCEにデプロイしています。ずっと動き続けるタイプのbotなのでGCEを選びました。
discord.js
discord.jsはDiscordを簡単に操作するためのライブラリです。チャンネルやメッセージの操作、ボイスチャットへの参加や発話などができます。DiscordのAPIについて調べなくてもbotを動かすことができたので、大変助かりました。
困った点としては、discord.jsの音声再生周りが新しめのNode.jsと相性がよろしくないらしく、音声が途中で途切れるという問題が発生しました。結局、音声再生問題の出ないNode.js v8までバージョンを下げることになったのですが、サポート期間とか考えるとだいぶ不安です。
Cloud Text-to-Speech
GoogleのCloud Text-to-Speechはテキストから音声合成を提供してくれるサービスです。Speech-to-Text(音声から文字起こし)の逆ですね。
https://cloud.google.com/text-to-speech?hl=ja
1ヶ月あたり400万文字まで無料で使えるので、ちょっとした使い方なら無料枠をオーバーすることはありません。ガンガン使っていけます。ただし他のText-to-Speechサービスと同じく日本語はあまり流暢ではなく、少し機械的な感じになってしまいます。そこは我慢しましょう。
制作期間
ライブラリが大体揃っていたので1日ぐらいでできました。先述の音声周りのバグには苦しめられましたが、あとはそんなに詰まるポイント無かったです。
実際に導入してみて
このbotを導入してから2ヶ月ほど経ちましたが、メンバーからの評判は良いです。今まで断絶が起こっていた聞き専に近い人たちとの交流も活発になりました。喋れない/喋りたくないという人でもボイスチャットに参加できるようになって、だいぶ賑やかになりました。
喋太郎を導入しても同様の効果は得られたとは思いますが、やはり自分で作るのは楽しかったです。皆さんも「車輪の再発明」とか気にせず作りたいものをガンガン作っていけばいいと思います。その方がきっと楽しいです。
さいごに
Discordって機能複雑だしボイチャ周りとかややこしそ〜というイメージがありますが、今は各種ライブラリが整備されているので簡単に作ることができます。発想次第で色々できると思うので、bot作りに手を出してみてはどうでしょう。
こうやって日常生活の片手間に小さな問題を解決していけると、とても楽しいです。このbotは規模としては非常に小さなものですが、ちょっとだけでも誰かの役に立てているという実感はとても大きく響いてきます。
実は手を出していないだけで、小さな労力で自分が活躍できる領域というのはきっといろんな場所に散らばっていると思うので、そういう細かな課題をどんどん見つけてどんどん解決していきましょう。
それでは、プログラミング楽しんでいきましょう〜。
- 投稿日:2020-02-13T00:10:39+09:00
vue-cli-plugin-express でポート番号を指定してサーバーを起動する
vue-cli 3.x プラグインの vue-cli-plugin-express にて、ポート番号を指定してサーバーを起動する際に詰まったので備忘録として。
この記事は vue のプロジェクトに
vue add express
でプラグインを追加した状態から進めていきます。解決方法
package.json
の"scripts"
内にある"express"
または"express:run"
の値の末尾に--port [任意のポート番号]
を付け加えることで、ポート番号を指定してのサーバー起動に成功しました。package.json"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "express": "vue-cli-service express:watch --port 3001", "express:run": "vue-cli-service express:run --port 3001" }> npm run express > test@0.1.0 express (プロジェクトのパス) > vue-cli-service express:watch --port 3001 DONE Wed Feb 12 2020 23:22:41 GMT+0900 (GMT+09:00) ♻️ Server running at: - Local: http://localhost:3001/ - Network: http://192.168.0.100:3001/ ⚙ You're in development mode. to start the application, run npm run serve. �? Fallback to this server enabled: you can use relative routes in your cod �? No api routes found (yet?).解決前に試したこと
プラグイン導入時に生成される
./srv/index.js
内にapp.listen(port, () => {})
の形で書いてもポート番号が反映されず、プラグインの README.md にポート番号の指定について書かれていなかったのでしばらく悩みました。node_modules の中を直に弄るのもなぁと思いながらファイルを漁ってみたところ、それっぽいことが書いてあるファイルが見つかりました。
→ vue-cli-plugin-express/src/config.js (GitHub)この時点でなんとなく指定方法がわかったのですが、何かの出力では…?と思い、
npm run express
を実行したときに呼び出されるvue-cli-service express:watch
に対して、--help
をつけて呼び出してみたところ、それらが出力されました。> npx vue-cli-service express:watch --help Usage: vue-cli-service express:watch [options] Options: --delay delays run by a small duration (default: false) --host specify host (default: 0.0.0.0) --port specify port (default: 3000) --https use https (default: false) For more info, see https://github.com/mathieutu/vue-cli-plugin-express今後はちゃんと
--help
を使っていこうと思いました…。