Gobble up pudding

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

MENU

上限付き境界ワイルドカードについて

f:id:fa11enprince:20200511185745j:plain Javaを使っていると、Clazz<Foo extends Bar>とかClazz<? extends T>というような
あまり見た目にやさしくない呪文を見かけると思います。
これはなんだ?って話です。
これは上限付きワイルドカード(upper bounded wildcard)といいます。

そもそもなんでこれが必要なのか

メソッドやクラスを使う場合を考えてみると
どんな型でも受け入れたい場合どうすればよいか。
その場合はObjectを使います。。。もしくは?(unbounded wildcard)を使います。。。
ってのは冗談で、そうすると使いたいメソッドがObjectにないことが多くて
警告でcastしてないよ!!ってのがしょっちゅう出てきます。 そこで出てくるのがジェネリクス(Generics)です。
List<E>とかがその典型ですね。
List<Integer>とすればInteger型でListが扱えます。
List<String>とすればString型でListが扱えます。

List<T>だけで十分ではないの?って思うと思います。 たいていはそれで充分なのですが、なんかたまに
? extends T
ってのを見かける。これJava特有だし。(C#にはwhereなるものがあるが…) これライブラリでよく見ると思いますが、自分での実装で使う機会はそう多くないため、
いつ使うんだ?ってなりがちです。

具体例

例えばですが、配列を使っていてソートをするのに
挿入ソートのクラスでどんな型でも受け入れたいってことにしたい場合、
こいつが必要になってきます。
すごい単純なクラスを作ってみます。
単にInsertionSorter<T>となっていないところがポイントです

import java.util.Arrays;

public class InsertionSorter<T extends Comparable<T>> {
    T[] data;

    public void sort(T[] data) {
        this.data = data;
        isort(data);
    }

    private void isort(T[] data) {
        for (int left = 1; left < data.length; left++) {
            for (int right = left; right > 0
                 && data[right - 1].compareTo(data[right]) > 0; right--) {
                swap(data, right - 1, right);
            }
        }
    }

    public void swap(T[] arr, int pos1, int pos2) {
        T temp = arr[pos1];
        arr[pos1] = arr[pos2];
        arr[pos2] = temp;
    }
    
    public static void main(String[] args) {
        Integer[] data = { 5, 4, 3, 2, 6, 7, 8, 1, 9 };
        InsertionSorter<Integer> sorter = new InsertionSorter<>();
        sorter.sort(data);
        Arrays.stream(data).forEach(i -> System.out.print(i + ","));
    }
}

オンラインで実行したい方はこちら: http://tpcg.io/bTn8mbKH

sortをinterfaceのメソッドにしてなくてStrategyパターンになれないから微妙ですが…例なのでシンプルに。

<T extends Comparable<T>>
Comparableを継承するTって意味です(extends継承だけでなくimplements継承も含まれます)。
要はComparableを継承するものに制限しています。
上記のクラスではcompareTo(T o)を使っています。
compareTo(T o)を使えるのはComparableな型でなくてはなりません。
ところが単にTとしてしまうとcompareTo(T o)なんてメソッドありませんとエラーになってしまいます。
ピンとこない方は、上記のURLでInsertionSorterのGenericsの部分を単にTに変えてみてください。 実行しようとするとエラーになると思います。 それを解決するためにこのT extends Comparable<T>というものがあります。
こうすると、「ほうほう、このTはComparableなんだな」とコンパイラがわかってくれてエラーにしなくなります。

参考リンク

https://www.ibm.com/developerworks/jp/java/library/j-jtp07018.html

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とかもほぼ一緒なので実は大して覚えることがないはず。