Elixirのplugがさっぱり理解できない
Elixirのplugという仕組みがさっぱり理解できないので、記事を書きながら理解を進めていくメモ
plug概要
plugのリポジトリはこちら。
自分なりにソースコードを見てみて理解したのは以下のような感じ。(違ってても責任はとれません)
- ElixirでWebアプリケーションを作るための仕組みである。
- webアプリケーションを構築するための情報を集約したconnという構造体をするのがPlugライブラリである。
- 関数plugとモジュールplugがある。
- 関数plugはconnとオプションを受け取り、何かしらの処理を加えて、加工したconnを返す。
- モジュールplugはinit/1とcall/2を定義し、initはオプションの初期化を行う。callはconnを更新して返す。
Plug公式Hello world
plugのREADMEに記載されているHello worldプログラムが何をしているのか少し見てみました。
plug/README.md at master · elixir-plug/plug · GitHub
まずはモジュールplugの用意。
defmodule MyPlug do import Plug.Conn def init(options) do # initialize options options end def call(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "Hello world") end end
そしてサーバーの起動。
$ iex -S mix
iex> c "path/to/file.ex"
[MyPlug]
iex> {:ok, _} = Plug.Adapters.Cowboy.http MyPlug, []
{:ok, #PID<...>}
これでポート4000でサーバーが立ち上がり、callで定義されている”Hellow world”の出力が行われる。
Plug.Adapters.Cowboy.http MyPlug, []の部分は、中身ではCowboyモジュールのhttp_startメソッドを呼び出していて、第2引数にplugを渡している。
それが以下の部分。
https://github.com/elixir-plug/plug/blob/master/lib/plug/adapters/cowboy.ex#L185
apply(:cowboy, :"start_#{scheme}", args(scheme, plug, opts, cowboy_options))
applyというのはモジュールのメソッドを呼び出す関数。
#{scheme}には「http」が渡ってきているので、
cowboyモジュールのstart_httpが呼ばれ、第2引数にplugが渡されている事が分かる。
Cowboyが中身で何をしているのか自分はよくわかっていないが、つまるところWebサーバーの出力に必要な情報をPlugを使って定義しているという事なんだろうと思う。
なんとなくわかってきた気がする。
調べながら徐々に加筆していこうと思います。
アプリケーションモジュールの設定
Elixirs Shoolの以下の項目も見てみましたが、なんかよく分かりません。
https://elixirschool.com/jp/lessons/specifics/plug/#プロジェクトのアプリケーションモジュールの設定
こういうコードを用意しろと書いてあります。何をやっているんだろうか。
defmodule Example do
use Application
require Logger
def start(_type, _args) do
children = [
Plug.Adapters.Cowboy.child_spec(:http, Example.HelloWorldPlug, [], port: 8080)
]
Logger.info "Started application"
Supervisor.start_link(children, strategy: :one_for_one)
end
end
まずここ。
children = [
Plug.Adapters.Cowboy.child_spec(:http, Example.HelloWorldPlug, [], port: 8080)
]
Plug.Adapters.Cowboy.child_spec
の中身を見てみます。(つづく)
参考サイト
「いまどきのJSプログラマーのためのNode.jsとReactアプリケーション」のサンプルコードのバグ。その2
「いまどきのJSプログラマーのためのNode.jsとReactアプリケーション」のサンプルコードを動かそうと思ったら動かなかった所があったのでメモ。その2。
「6章 03 機械学習で手書き文字を判定しよう」のコード、
「1-download.js」を実行すると、ファイルをダウンロードし終わるタイミングでエラーになってしまう事があるもよう。
↓以下の部分。
// ファイルのダウンロードを行う関数を定義 --- (※3)
function downloadPromise (url, savepath) {
return new Promise*1 return resolve()
const outfile = fs.createWriteStream(savepath)http.get(url, (res) => {
res.pipe(outfile)
res.on('end', () => {outfile.close() → ここをoutfile.end() にすると良いらしい。
resolve()
})
})
.on('error', (err) => reject(err))
})
}
ファイルへの書き込みが終わらないうちにclose()を呼び出すとエラーになるようです。
Dockerfileの書き方を調べる
Dockerを使い始めたので、便利な使い方をいろいろ覚えていこうと思います。
Dockerfileを使っていつでも同じ環境を構築する方法を、簡単なところから試してメモしていきます。
Dockerfileの使い方
Dockerfileという名前でファイルを作る。
Dockerfileを作ったディレクトリ上で「docker build .」コマンドを実行すると、ファイルの中身に従ってコンテナのセットアップが始まる。
FROMでベースとなるイメージを指定する
Dockerfileに「FORM イメージ名」と記述することでベースとなるイメージを指定します。
例えばDockerfileに以下だけ記述します。
FROM node:latest
Dockerfileを作ったディレクトリで「docker build .」を実行すると、nodeの最新バージョンがインストールされたイメージが作られるというかんじ。
FROMに指定できるイメージ名は以下のサイトのリストから選べる。
https://hub.docker.com/explore/
イメージ名は「名前:バージョン」という形になっているようです。
MAINTAINER
イメージを誰が作ったのかを書いておく場所。
個人のローカルでDockerfileを作るぶんにはどうでもいいと思いますが、公開したり会社でやる場合は名前を入れたほうが良いでしょう。
MAINTAINER funashimin
こんな感じ。
ENV
Dockerfile内で使う環境変数を設定する。パスを通したりするのが多い使い方だと思う。
ENV PATH $PATH:(追加したいパス)
ENVで宣言したパスはDockerfile内で「$変数名」の形式で参照することが出来る。
EXPOSE
コンテナが外部に公開するポート番号を指定できる。
EXPOSE 80
これに加えてコンテナ起動時にポートのバインド指定をすると外部からアクセスできるようになる。
-pオプションを使って-p8080:80とかする事で、ホスト側で8080にアクセスするとコンテナの80番ポートに転送される。
CMD
コンテナ起動時に実行するコマンドを一つだけ指定できる。
コンテナの初期処理をシェルにまとめておいて、それが実行されるようにしておくのが一般的な使い方になると思います。
CMD ["コマンド","オプション1”,"オプション2"…]
ENTRYPOINT
CMDと似ている。コマンドを一つだけ実行できる。
docker runで渡したコマンドライン引数が、ENTRPOINTで指定したコマンドの引数として渡されるらしい。
RUN
コマンドを実行し、結果をイメージにコミットする。
CMDがコンテナ実行時の初期処理なのにたいして、RUNはイメージを作成する時に実行したいコマンドを書くという感じか。
ADD
イメージに追加するファイルを指定する。
ADD コピー元 コピー先
Docker for Windowsを使う。(Windows10向け)
私は普段からWindowsのノートパソコンを使っているのですが、新しいプログラミング言語やフレームワークを試そうと思うと、Windows上に直接環境を作るのには何かと困ります。
つい最近も、勉強会に参加して環境構築でつまづくという出来事が続いたので、今更ですが、Dockerを使ってみる事にしました。
簡単な導入手順を自分用にメモしておきます。
- 対象環境
- Hyper-Vを有効化する
- Docker for Windowsをインストールする
- 試しにnodejsでサーバーを動かす
- Dockerの状態を保存する
- コンテナIDを調べる
- おまけ(エラー対策など)
対象環境
64ビット版Windows10 Proで試しています。
homeエディションではHyper-Vが使えないらしいので、その時はVirtualBoxなどが必要とのこと。
Hyper-Vを有効化する
Windows PowerShellを管理者で実行して以下のコマンドを実行し、マシンを再起動する。
> Enable-WindowsOptionalFeature -Online -FeatureName:Microsoft-Hyper-V -All
Docker for Windowsをインストールする
以下のサイトからダウンロードしてインストール。
https://docs.docker.com/docker-for-windows/install/
ここまででDockerのセットアップは完了。
試しにnodejsでサーバーを動かす
docker run
nodejsでサーバーを立ち上げてローカルで動作確認をする。
コマンドプロンプトで以下のように打ち込む。ローカルの8080ポートをDocker環境に転送するようにしている。
> docker run -p 8080:8080 -it node /bin/bash
Docker環境が構築されてログインできる。最初は少し時間がかかる。
npmはインストールされている状態になっている。
vimをインストール
どうやらviが入っていないので入れる。何かしらファイルを作って中身を書ければviじゃなくてもいい。
> apt-get update
> apt-get install vim
node init
てきとうな作業ディレクトリを作ってnodeのプロジェクトを作る。
> npm init
動作確認なので全てデフォルトのままでいく。いろいろ聞かれますが全てEnterを押していく。
npm install
> npm install
する
index.jsを作る
プロジェクトのディレクトリにindex.jsを作って以下のような内容にする。
動けばなんでもいいが、ポート番号は最初に転送の指定をした番号にしておく。
app.set('port', 8080);
app.get('/', function(request, response) {
response.send('Hello World')
});app.listen(app.get('port'), function() {
});
node index.js
node index.js
とコマンドを入力してサーバーを起動する。
ブラウザでhttp://localhost:8080と打てばウェブページが表示されると思います。
Dockerの状態を保存する
そのままctrl+DとかでDocker環境を抜けてしまうと作業が全て消えてしまうので、保存するようにします。
detachする
ctrl+p ctrl+qと入力してコマンドプロンプトに戻る。
Dockerコンテナを起動したまま抜けるのをdetachというらしい。
コンテナIDを調べる
> docker ps
とうつとコンテナの一覧が出る。今起動していた「CONTAINER ID」の値を見る。
commitする
> docker commit (コンテナID) (イメージ名)
というコマンドでコンテナの状態を保存する。
イメージ名は自分が分かりやすいものにする。
ここまででコンテナの保存が完了する。
attachする
コンテナに戻りたい場合はattachする
> docker attach コンテナID
おまけ(エラー対策など)
Error response from daemon: Bad response from Docker engine
dockerのコマンドを入力した時に以下のようなエラーが出る事がある。
Error response from daemon: Bad response from Docker engine
Dockerのアプリケーションを開き(タスクトレイのDockerアイコンからsettingを選択する)、左メニューの「Reset」を選び、「Reset to factory defaults...」を実行するとなおる。
以上。とりあえずこれでWindows10でDockerが動かせるようになった。
あとは自分用の環境をあらかじめいくつか用意しておくと、勉強や検証がはかどると思う。
Windows10ユーザーの方は特に助かるのではないかと思う。(私はとても助かる)
そして、マイクロソフトさんはWindowsマシンがエンジニアに選ばれるように、頑張ってほしい。
Elixir:エリクサー~関数の定義方法まとめ
プログラミング言語Elixirの、関数定義の書式をまとめます。
1行で書く、複数行で書く、省略形など、いくつかパターンがあります。
1行で関数を定義して変数に格納する。
定義
aという引数を取り、aを出力する関数。
iex()> func1 = fn a -> IO.puts a end
#Function<6.99386804/1 in :erl_eval.expr/5>
実行
iex()> func1.(1234)
1234
iex()> func1.(:functiontest)
functiontest
:ok
関数定義の省略表現
&()で関数を定義できます。
引数は&1、&2、&3…で参照できる。
以下は引数3つの和を返す関数。
iex()> func3 = &(&1+&2+&3)
#Function<18.99386804/3 in :erl_eval.expr/5>
実行
iex()> func3.(1,2,3)
6
複数行の関数を定義する
複数行の関数を書くとき。1行の時とさして変わらない。
インデントはスペース2つぶんにするのがElixirでは普通らしい。
iex()> func4 = fn name ->
...()> output = "Hello "<>name
...()> IO.puts output
...()> end
#Function<6.99386804/1 in :erl_eval.expr/5>
実行。
iex()> func4.("taro")
Hello taro
:ok
引数のパターンマッチを利用して処理を分ける
引数のパターンマッチを使って処理を分けるのがElixirらしさ。
これによりif文を書かないようにするのがElixirらしさ。
1つ1つのブロックが小さくなり、コードが美しく保たれる。
引数がtaroとjiroの時は「I am taro」という形で出力する。
それ以外は引数をそのまま使って、「Other name:(nameの値)」を出力する。
iex()> func5 = fn
...()> "taro" -> IO.puts "I am taro"
...()> "jiro" -> IO.puts "I am jiro"
...()> name -> IO.puts "Other name:" <> name
...()> end
#Function<6.99386804/1 in :erl_eval.expr/5>
ちなみに、関数の引数のパターンの部分を「ヘッド」と言うらしい。この例では、「3つのヘッドがある」という事になる。
実行結果
iex()> func5.("taro")
I am taro
:ok
iex()> func5.("jiro")
I am jiro
:ok
iex()> func5.("saburo")
Other name:saburo
:ok
引数をピン(^ ←これ)で固定する
Elixirにはピン演算子というのがあって、
変数名の頭に「^」を付けると最初にマッチさせた値に変数の内容を固定させることができる。
関数の引数にもこれを使うことが出来る。
定義
iex()> func6 = fn
...()> (^name) -> "first call: #{name}"
...()> (_) -> "second call"
...()> end
#Function<6.99386804/1 in :erl_eval.expr/5>
実行
iex()> func6.("hoge")
"first call: hoge"
iex()> func6.("piyo")
"second call"
iex()> func6.("hoge")
"first call: hoge"
最初に"hoge"という値を渡すと、nameの値が"hoge"に固定される。
シングルトンパターン的な使い方できるのだろうか…
Elixir:エリクサー~シジルのメモ (~から始まるやつ)
プログラミング言語Elixirのメモ。
シジルに関する文法のメモです。
~(チルダ)から始まる文法をシジルと呼ぶようです。
シジルとは魔術で使う図形や記号を意味するらしい。
~w:Word list(単語のリスト)
以下のようにすると文字列のリストを作る。
iex> ~w(abc def ghi)
["abc", "def", "ghi"]
~w()の中に連続した文字列をスペースで区切ってあげると良い。
後ろにaを付けるとアトムのリストになる。
iex> ~w(abc def ghi)a
[:abc, :def, :ghi]
cを付けるとキャラクターのリストになる。
iex> ~w(abc def ghi)c
['abc', 'def', 'ghi']
ダブルクォートがシングルクォートになった…
Elixirでは文字リストと文字列のリストは違うらしい。あとでよく調べておこう。
ヒアドキュメントを使って縦に書いていくこともできる。
iex> ~w"""
...> abc
...> def
...> ghi
...> """
["abc", "def", "ghi"]
~W:単語のリスト。エスケープ不可
エスケープ不可な単語のリストを作る