Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

Express 4 + EJS + Socket.IOでWebSocketをやってみる

f:id:fa11enprince:20200416000912j:plain WebSocketをNode.js(Express 4 + EJS + Socket.IO)で復習がてら実装しました。 ハマりポイントはbin/wwwを修正しないと動いてくれないところ。
Expressってシンプルで綺麗な設計のフレームワークなんですが、
結構はまりやすいポイントが多いと思います。

Nodeおよびライブラリのバージョン

Node.js   12.14.1
Express    4.16.1
socket.io  2.3.0
EJS        2.6.0

最終成果物

次のと同じものを作る手順を記載します。

ひな形を作成

まずexpress-generatorをグローバルインストールをします。

$ npm install express-generator -g

次にひな形生成をします。いわゆるスキャフォールディングです。 プロジェクトのフォルダはexpress-ws-ejsとします。 テンプレートはEJSを選択します

$ cd express-ws-ejs
$ express --ejs

ソースコードを修正

まずsocket.ioを使えるようにします。

$ npm i socket.io --save
$ npm i

/models/chat.js

WebSocketのサーバ側です。 新規作成します。modelsフォルダを作り、chat.jsを作成します。

const socketio = require('socket.io');

function chat(server) {
    const sio = socketio.listen(server);
    sio.on('connection', function(socket) {
        socket.on('chat-message', function(msg) {
            console.log('Send message to client');
            sio.emit('chat-message', msg + '💛');
        });
        socket.on("disconnect", function() {
        });
    });
};

module.exports = chat;

■補足
いくつかあるemitの違い

const sio = socketio.listen(server);
sio.on('connection', function(socket) {
    socket.emit('chat-message', 'message'); // 送信元クライアントだけに送信
    socket.broadcast.emit('chat-message', 'message'); // 送信元を除く全クライアントに送信
    sio.emit('chat-message', 'message'); // 接続されている全クライアントに送信
}

他にも似たようなのがありますが、これだけ押さえておけばよいでしょう。
javascript - Send response to all clients except sender - Stack Overflow

/bin/www

ここでsocket.ioのlistenをしている/models/chat.jsを呼ばないといけません。
chat.jsはのちほど作成します。 app.jsでこれを呼ぶことはできません。というのもcreateServerをしているのが
/bin/wwwだからだと思われます。 ちなみにですが、varじゃなくてconstがいいのですがexpress-generatorvarで作るので、
varのままにしていたりします。

@@ -5,7 +5,8 @@
  */
 
 var app = require('../app');
-var debug = require('debug')('express-ws-ejs-o:server');
+var chat = require('../models/chat');
+var debug = require('debug')('express-ws-ejs:server');
 var http = require('http');
 
 /**
@@ -28,6 +29,7 @@
 server.listen(port);
 server.on('error', onError);
 server.on('listening', onListening);
+chat(server);
 

/app.js

usersのrouterはいらないので消します

@@ -5,7 +5,6 @@
 var logger = require('morgan');
 
 var indexRouter = require('./routes/index');
-var usersRouter = require('./routes/users');
 
 var app = express();
 
 @@ -20,7 +19,6 @@
 app.use(express.static(path.join(__dirname, 'public')));
 
 app.use('/', indexRouter);
-app.use('/users', usersRouter);
 
 // catch 404 and forward to error handler
 app.use(function(req, res, next) {

/controllers/index.js

これは新規作成します。controllersフォルダを作り、index.jsを作成します。

exports.index = function(req, res) {
    res.render('index', { title: 'Express' });
}

/routes/index.js

var express = require('express');
var router = express.Router();
var index_controller = require('../controllers/index');

router.get('/', index_controller.index);

module.exports = router;

その他リソースおよびview

主にクライアント側です。
Node使ってるとクライアント側なのかサーバ側なのかたまに混乱します。
/public/javascripts/index.js

$(function() {
    const socket = io('http://localhost:3000');
    $('form').submit(function() {
        console.log($('#m').val());
        socket.emit('chat-message', $('#m').val());
        $('#m').val('');
        return false;
    });
    socket.on('chat-message', function(msg) {
        $('#messages').append($('<li>').text(msg));
        window.scrollTo(0, document.body.scrollHeight);
    });
});

/public/stylesheets/index.css

* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
#messages { margin-bottom: 40px }

/views/index.js

@@ -2,10 +2,18 @@
 <html>
   <head>
     <title><%= title %></title>
-    <link rel='stylesheet' href='/stylesheets/style.css' />
+    <link rel='stylesheet' href='/stylesheets/index.css' />
   </head>
   <body>
-    <h1><%= title %></h1>
-    <p>Welcome to <%= title %></p>
+    <ul id="messages"></ul>
+    <form>
+        <input id="m" autocomplete="off">
+        <button type="submit">Send</button>
+    </form>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
+    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
+        integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
+        crossorigin="anonymous"></script>
+    <script src="javascripts/index.js"></script>
   </body>
 </html>

Expressの起動

最後にExpressを起動すれば終わりです。

$ npm start

http://localhost:3000 にアクセスし、ブラウザのウィンドウを2つ以上立ち上げると動作確認ができます。 f:id:fa11enprince:20200416002210p:plain

参考

https://socket.io/get-started/chat/
https://liginc.co.jp/web/programming/node-js/132081
https://developer.mozilla.org/ja/docs/Learn/Server-side/Express_Nodejs
https://www.gitignore.io/api/node

WindowsでGitLabを構築(Docker)

f:id:fa11enprince:20200413013004j:plain
Docker DesktopによるGitLab構築方法の紹介です。
一応、初心者向きにはなっていますが、
Dockerの基礎知識は知っている前提の記事です。
Windows + Docker Desktop (旧版 Docker for Windowsでも可)で構築します。
Windowsでも動けば、Mac/Linux等でも動くでしょう。
Windows固有のハマりポイントはマウントの設定でしょうか

Dockerは気づくとITエンジニアほぼ必須の知識になってしまいました。
正直ここまで広まるとは思っていませんでした。
一部環境ではVT-x(インテル 仮想化支援機構)がONにするのを許されていなくて、
手元でDocker使えない、不便だーなんてことが起きてますが、
その場合はあきらめて開発サーバーを建てましょう(わたしもご多分に漏れず…)。
Docker使う場合は基本的にはLinuxがベターではあります。
とはいえどういうわけか手元にはWindowsマシン(Windows 10 Pro Edition)しかないみたいなときとか役に立つかと。

この記事について

docker-composeにてサクッとGitLabを立ててみようという記事です。
ただ、デフォルトのままではメール設定どうすんの?
とかWindowsの場合、ちょっとハマりがあったりとか、
そもそも遅いとか言った問題があるかと思います。
GitLabで使うデフォルトはPostgreSQLです。
https://docs.gitlab.com/omnibus/settings/database.html

こだわりがなければこのまま使います。 ちなみに、Dockerを使っている人はご存知かと思いますが、 Docker内のデータは一度止めてしまうと基本的にすべて消えてしまいます。
そのために、ホスト側にデータ領域をマウントし、再起動時にそこから読むことで永続化を行っています。

前提条件

私の環境は以下です。多少異なっていてもおそらく大丈夫かと思います。
かなり古い場合でなければ、特にDocker for Windowsでも大丈夫かと。

OS/ソフト バージョン
Windows 10 1903
Docker Desktop community 2.2.0.4

設定方法

Docker Desktopおよびホスト側の設定

Settings > Resources > File Sharing でCドライブを選びます

docker volume の作成

Windowsでは起動時に権限がらみのエラーが発生してしまうので、
マウントするのに名前付きボリュームを利用します。 docker volume lsでその一覧があとで参照できます。
実際にどこにマウントされているかはDocker DesktopのDashBoardのコンテナのInspectから参照することもできます。

docker-composeの設定

ドキュメント(Documents)ディレクトリ配下
%USERPROFILE%\Documents
docker\gitlab
というフォルダを作り、そこにdocker-compose.ymlを作成します
今回はPostgreSQL使用、
メールはGmail使用
としました。 この辺りの設定(GITLAB_OMNIBUS_CONFIG [/etc/gitlab/gitlab.rb])は
gitlab-ctl reconfigureで設定できる内容です(詳細割愛)。
GitLab的にはGmailは非推奨のようですが、例として使いやすいのでGmailで説明します。
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md
下記に示した内容をいくつか置き換えて社内メール等を設定すれば環境構築できるかと思います。
大抵のケースの場合SMTP周りの設定をしておけばいいと思われます。

version: "3.6"

services:
  web:
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    hostname: '127.0.0.1'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://127.0.0.1:8929'
        gitlab_rails['gitlab_shell_ssh_port'] = 50022
        gitlab_rails['smtp_enable'] = true
        gitlab_rails['smtp_address'] = "smtp.gmail.com"
        gitlab_rails['smtp_port'] = 587
        
        gitlab_rails['smtp_domain'] = "smtp.gmail.com"
        gitlab_rails['smtp_authentication'] = "login"
        gitlab_rails['smtp_enable_starttls_auto'] = true
        gitlab_rails['smtp_tls'] = false
        gitlab_rails['smtp_openssl_verify_mode'] = 'peer'

        gitlab_rails['smtp_user_name'] = "<your gmail address>"
        gitlab_rails['smtp_password'] = "<your passowrd>"
        
        # Gitlab tuning
        unicorn['worker_processes'] = 2
        unicorn['worker_timeout'] = 600
        postgresql['shared_buffers'] = "512MB"
        
    ports:
      - '8929:8929'
      - '50022:22'
    volumes:
      - 'gitlab_config:/etc/gitlab'
      - 'gitlab_log:/var/log/gitlab'
      - 'gitlab_data:/var/opt/gitlab'

volumes:
  gitlab_config:
    external: true
  gitlab_log:
    external: true
  gitlab_data:
    external: true

1点注意事項があります。 2段階認証プロセスを使っている場合、あれれ?メールが飛ばないなんてことがあります。

Gmail を使って Net::SMTPAuthenticationError が出力される場合の解決法 https://karur4n.hatenablog.com/entry/2015/03/07/234600

この場合上記に従い、アプリパスワードを使います。

社内メールを使用する場合

仕事で使う場合は、社内メールだと思いますので
doc/settings/smtp.md · master · GitLab.org / omnibus-gitlab · GitLab
あたりの設定が参考になるのではないでしょうか。

パフォーマンスチューニングについて

GitLabは結構マシンパワーを要求するのでpostgresql['shared_buffers']を大きめにとったほうが良いかと思います。
できれば2GB以上あったほうがサクサク動きます。
unicorn['worker_processes']も多いほど良いです。

名前付きボリュームの作成

コマンドプロンプトにて初回のみ

docker volume create --name gitlab_config
docker volume create --name gitlab_data
docker volume create --name gitlab-logs

として名前付きボリュームを作成します。

サーバー立ち上げ

コマンドプロンプトにて

cd %USERPROFILE%\Documents
docker-compose up -d

で起動します。

立ち上がりはマシンによっては遅いのでカップラーメンを食べながら待ちます。
Dockerのタスクトレイ内のアイコンを右クリックして、
Dashboardにアクセスすると、起動状態が確認できます。

GitLabにアクセス

しばらくたつと、 http://127.0.0.1:8929/
にアクセスします。

初回はrootのパスワードを求められます。

ユーザを作成した時にメールが飛べば成功です。

あとは煮るなり焼くなり。
GitHub同様使いやすいかと思います。

参考リンク

https://docs.gitlab.com/omnibus/docker/
https://mikoto2000.blogspot.com/2018/06/gitlab-docker-image-https.html https://myamada9999.hatenablog.com/entry/2018/11/04/204613
https://qiita.com/TomoyukiSugiyama/items/b17800d4e142e3dc549b
https://qiita.com/comefigo/items/2066dd02e2a0f3b470ca

チューニング系参考リンク

http://www.sakutyuu.com/technology/?p=1979
https://qiita.com/k_nakayama/items/9f083a4700915d02104a

名前付きボリューム関連

https://tan-y.hatenablog.com/entry/2019/02/26/003000
https://mikoto2000.blogspot.com/2018/06/gitlab-docker-image-https.html

Docker Desktopのマウント問題

https://qiita.com/kikako/items/7b6301a140cf37a5b7ac
https://stackoverflow.com/questions/41036273/disable-autostart-of-docker-compose-project

docker volume削除

https://qiita.com/Ikumi/items/b319a12d7e2c9f7b904d

メール関連

https://h-otter.hatenablog.jp/entry/2015/07/31/220511
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md

GitLab CE Omnibus の基本操作手順

https://fumiyas.github.io/gitlab/install-omnibus.html

Dockerのコマンド体系がわかりにく過ぎる

f:id:fa11enprince:20200408042801p:plain Docker自体は素晴らしいアプリケーションで、作っていただいている方、メンテナンスをされている方に畏敬の念を禁じえません。
CLIのフロントエンド、コマンド体系が理解するのが難しいと言わざるを得ません。理由としては、一見よく使うコマンドの対称性がないために、Dockerのライフサイクルとのイメージが慣れるまでズレてしまい、マッピングがよくわからずとっつきにくい印象を与えてしまうと思われるのです。
これはおそらく私だけではないはず。説明するときに毎度苦労します。
dokcer execdocker runって似ているけど何が違うの?とかdocker startもあるし、まぎらわしくない?とか、docker rmとかよくわからない…となると思います。
同様のことはGitにもあると思っています。
dockerの新体系のコマンドもありますが、こちらでは紹介しません。(一貫性がやや出てきたものの…)そもそもタイプ量が増えていやです。 ここでは旧体系のコマンドで記載します。

Dockerのライフサイクル

まず、Dockerのライフサイクルですが、この図が最小限の重要なところを表しています
f:id:fa11enprince:20200408034301p:plain

Dockerのコマンド体系

異論はあると思いますが、上図にマッピングするとこんな感じです

dockerの実際のコマンド 用途 備考
docker create image . docker build . Dockerイメージを作成 | cdして Dockerfileのあるディレクトリで使用
docker create [イメージ名] --name [プロセス名] Dockerイメージからコンテナを作成 複合コマンドrunがあるので使わないが、概念上知る必要がある
docker start [コンテナ名] Dockerコンテナからプロセスを開始状態にする 複合コマンドrunがあるので使わないが、概念上知る必要がある
docker run [コンテナ名] --name [プロセス名] Dockerイメージからコンテナを作成しプロセスを開始状態にする このコマンドが最難関(後述)
docker stop [プロセス名] プロセスを終了状態にする
docker rm [コンテナ名] Dockerコンテナを破棄する
docker rmi [イメージ名] Dockerイメージを破棄する

docker runについてですが、サーバ起動の場合(例: Express, Flaskの場合) docker run -itd --rm [コンテナ名] --name [プロセス名]のように使います。 単発起動の場合は docker run -it --rm [container name] --name [プロセス名] のように使います(通常は、DockerfileのCMDで指定されているものを実行したい場合が多いと思うが、lessとか使いたい場合はコマンドも指定できる)。さらに実質これを使うときは-p(ポートを外部にさらす)や-v(マウント)のオプションも必要になるケースが多いと思います。 あと、とっつきにくい点があるとすればdocker run時のオプション順を間違えるとエラーになってしまう点でしょうか。

番外編

Docker execいつ使うの?

docker exec -it [container name] /bin/bash[/bin/sh] の形でほぼ使い、バックグラウンドで動きっぱなしのコンテナの中に入る場合に使う
というか、バックグラウンド起動している場合以外中に入る必要性がほぼない。
これもdocker in [container name] [shell]とか作ればいいのにとか思う。

docker attachはまず使わないので無視してOK

あとはprocess確認系のコマンドも必須で覚える事項ですが、 これだけ覚えてしまえばdocker-composeとかもほぼ一緒なので実は大して覚えることがないはず。

JavaScriptで使えるグラフ描画ライブラリとその有効活用例について

f:id:fa11enprince:20200327025625j:plain ふとニュースなどをみていて、新型コロナウィルスの感染状況のグラフのサイトを見つけて、お、綺麗だしシンプルでいいなと思ったのがここ。
よくある質問の部分も、一見、グラフを眺めていて、ん???と思う疑問点が書かれてあって良い。

「具体的な基準はMITライセンスに準拠します」とさらりと書いてあるだけなのがクール。もちろんGitHubへのリンクもあります。 これ厳密に従うとちょっと面倒なんですよね。とはいえ、数あるライセンスの中でも緩い縛りのMITライセンス。
単なるリンクまたはiframeであれば著作権表示はいらないのですが、せっかくなのでスクリーンショットを拝借しました。
ということで厳密に対応してみました。ただ、ライセンスファイルはブログの関係上置けないので、貼り付けました。 f:id:fa11enprince:20200327031353p:plain https://github.com/kaz-ogiwara/covid19/

MIT License

Copyright (c) 2020 Kazuki OGIWARA / 荻原 和樹

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

一応これは何だっていうのを解説しておくと要はMITの場合

  • このソフトウェアを誰でも無償で無制限に扱って良い。ただし、著作権表示および本許諾表示をソフトウェアのすべての複製または重要な部分に記載しなければならない。
  • 作者または著作権者は、ソフトウェアに関してなんら責任を負わない。

という2点が大事です。

本題とはそれますが、こういうのって、いかにデータを集めるかというのが大変ですよね・・・。

Chart.jsいいなと思って、ふとグラフ描画ライブラリって何がいいんだろうなーとふと思いました。 自分が使ったことあるのはPlotly.js。d3.jsを拡張していてインタラクティブで綺麗で、きめ細かい設定が簡単にできて仕事で使ってました。 PythonでもJavaScriptでも使えるし。 が、このChart.jsもいいなと。あと有償ですが有名どころだとHighcharts.jsもありますよね。

代表的なグラフライブラリのリンク

https://www.chartjs.org/
https://plotly.com/javascript/
https://www.highcharts.com/

比較

https://tech.willgate.co.jp/entry/2018/03/20/141000
https://www.npmtrends.com/chart.js-vs-highcharts-vs-plotly.js-vs-taucharts
Chart.js強いなーという感じです。

僕はPlotly.js推しです(というかそれしか使ったことない)。 リファレンスもまぁまぁ充実しているし、細かいことをやろうと思うと、ソースコードを読めば、 あ、これも設定できるんだとわかるし。よかったです。 あれなんですよね。等高線(コンター)図で綺麗なのを描きたかったり、場合によっては3Dにしたいなと思ったので…前回はそれを選んだのかな。 でも、周りでChart.jsのこともよく聞くので、今度使ってみようかなと思いました。