Nginx (reverse proxy) + Phoenix でschemeを強制的にhttpsにする
memo: たどり着くのに時間が掛かったで残しておくことにした.
課題
- oauth認証にueberauthのライブラリを使っていた
- callback urlを動的に生成させるときに
Plug.Conn
のschemeを参照している - backend側のサーバーはhttpで動かしたい
- それだと Plug.Connのschemeはhttpなので、https のcallback_urlを生成できない
解決方法
Plug.SSLのプラグを使って強制的にhttpsにschemeを変更しました.
以下、phoenix側のconfigの設定です.
config :my_web, MyWeb.Endpoint, url: [host: "localhost", scheme: "https", port: 443], force_ssl: [ host: nil, rewrite_on: [:x_forwarded_proto] ]
上記のように :force_ssl
の設定を追加します.
(参考はここ Using SSL – Phoenix v1.3.0-rc.2.)
Forcing requests to use SSL:
In many cases, you’ll want to force all incoming requests to use SSL by redirecting http to https. This can be accomplished by setting the :force_ssl option in your endpoint. It expects a list of options which are forwarded to Plug.SSL. By default it sets the “strict-transport-security” header in https requests, forcing browsers to always use https. If an unsafe request (http) is sent, it redirects to the https version using the :host specified in the :url configuration.
記述を読むと、endpointのconfigの:force_sslの設定がPlug.SSLのオプションになるようです.
また、nginx側は以下の設定が必要です.
... location / { ... proxy_set_header X-Forwarded-Proto $scheme; # ここの一行が必要 proxy_pass http://backend/; } ...
これは、Plug.SSLの仕様で :rewrite_on
に設定したヘッダー名がrewriteされるためです.
:rewrite_on - rewrites the scheme to https based on the given headers
なので、そのヘッダーをproxy_set_headerしています.
(apacheなどの他のproxyサーバーでも同様な設定を入れれば期待する挙動になると思います.)
Plug.Connの確認
%Plug.Conn{ adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<0.116269836/1 in Plug.CSRFProtection.call/2>, #Function<4.117387578/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.58261320/1 in Plug.Session.before_send/2>, #Function<1.112466771/1 in Plug.Logger.call/2>, #Function<0.61641163/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>], body_params: %{}, cookies: %{ "_my_web_session_id" => "***" }, halted: false, host: "localhost", method: "GET", owner: #PID<0.467.0>, params: %{}, path_info: [], path_params: %{}, port: 80, private: %{ ***.Router => {[], %{}}, :phoenix_action => :index, :phoenix_controller => ***.PageController, :phoenix_endpoint => ***.Endpoint, :phoenix_flash => %{}, :phoenix_format => "html", :phoenix_layout => {***.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix_router => ***.Router, :phoenix_view => ***.PageView, :plug_session => %{"_csrf_token" => "gnjVVSwCXhyfv4pu4rBfVA=="}, :plug_session_fetch => :done }, query_params: %{}, query_string: "", remote_ip: {172, 21, 0, 6}, req_cookies: %{ "_my_web_session_id" => "***" }, req_headers: [ {"host", "localhost"}, {"x-forwarded-for", "172.21.0.1"}, {"x-real-ip", "172.21.0.1"}, {"x-forwarded-proto", "https"}, {"cache-control", "max-age=0"}, {"upgrade-insecure-requests", "1"}, {"user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"}, {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"}, {"referer", "https://localhost/"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7"}, {"cookie", "_my_web_session_id=***"} ], request_path: "/", resp_body: nil, resp_cookies: %{}, resp_headers: [ {"cache-control", "max-age=0, private, must-revalidate"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}, {"x-download-options", "noopen"}, {"x-permitted-cross-domain-policies", "none"} ], scheme: :https, script_name: [], secret_key_base: :..., state: :unset, status: nil }
scheme:
の部分が https
であることが確認できました.
参考
erlangでsnowflakeを実装してみた
はじめに
erlang製のsnowflakeほしいなと言う思いで色々探していて、思い描く感じのがなかったので自作してみた話。
リンク: GitHub - tkyshm/esnowflake: Uniq id generator based on Twitter's snowflake in Erlang
実装
snowflakeのアルゴリズムに関しては以下を参照していただければわかるはず。
www.slideshare.net
snowflakeのnodeは最大1024台立ち上げることができ、machine_id に0~1023を割り当てることが出来ます。
esnowflakeでは、この設定をworker_min_max_id
で設定できるようにし、
1つのesnowflakeで複数のmachine_idを持てるように設計しています。
以下、アプリケーションの設定で起動するworker数を設定できます。
[ {esnowflake, [{worker_min_max_id, [0, 1]}]} ]
使い方
% IDを生成 > Id = esnowflake:generate_id(). 896221795344384 % IDを複数生成 > esnowflake:generate_ids(2). [896498611015681,896498611015680] % IDからUnixTime(ミリ秒)取得 > esnowflake:to_unixtime(Id). 1509193995927 % IDからUnixTime(秒)取得 > esnowflake:to_unixtime(Id, seconds). 1509193995 % UnixTime(ミリ秒)からID(最小値)を知りたいとき > esnowflake:unixtime_to_id(1509193995927). 896221795319808 % UnixTime(秒)からID(最小値)を知りたいとき > esnowflake:unixtime_to_id(1509193995, seconds). 896217907200000
generate_idのベンチ
# (1個生成を100,000回施行した平均値) ---------------------------------------------------- 2017-10-29 16:03:27.671 b_generate_id 100000 4564.13864 ns/op ---------------------------------------------------- 2017-10-29 16:03:28.703 b_generate_id 232502 op/sec %%% esnowflake_SUITE ==> bench.b_generate_id: OK # (100個生成を10,000回施行した平均値) ---------------------------------------------------- 2017-10-29 16:03:29.700 b_generate_ids_100 10000 96834.8342 ns/op ---------------------------------------------------- 2017-10-29 16:03:30.702 b_generate_ids_100 686 op/sec %%% esnowflake_SUITE ==> bench.b_generate_ids: OK
おまけ(elixirからつかう)
# mix.exsのdeps defp deps do [ {:esnowflake, "~> 0.2.0"} ] end
elixir/sample » iex -S mix Erlang/OTP 20 [erts-9.0.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> t = :esnowflake.generate_id() 20433515594121216 iex(2)> t = :esnowflake.to_unixtime(id) 1513852072018 iex(3)> t2 = :esnowflake.to_unixtime(id, :seconds) 1513852072 iex(4)> :esnowflake.unixtime_to_id(t2, :seconds) 20433608900608000 iex(5)> :esnowflake.generate_ids(10) [20434393545850889, 20434393545850888, 20434393545850887, 20434393545850886, 20434393545850885, 20434393545850884, 20434393545850883, 20434393545850882, 20434393545850881, 20434393545850880]
感想
1台1台にworker数の設定をするのは面倒な気がするので、worker idを動的に割り当てできる方法は考え中です。 何かいい方法を思いついて、時間があれば追加で作ろうかと思ってます。
台数制限があるのにworker数が取得できないのは問題あるので、取得できるようにする予定。
おまけのは元々elixirからも使うのを考えてたのでついでに載せておきました。
参照URL
erlangでfluentdクライアントのOTPアプリケーションを実装した
はじめに
アプリケーションにログ、データを集めたりするのにしばしば利用される Fluentd というソフトウェアのクライアントをErlang/OTPで実装した話です。
本家のerlangのfluentdのクライアント の実装を眺めていて、 gen_eventで実装されていたため、過負荷や障害時などを考慮したい場合はひと工夫を入れる必要がありそうと思い、 今回本家と異なるビヘイビアで実装してみました。
何故gen_event使わなかったのか?
結論から言うと、別システムにデータを送信するようなケースでのgen_eventの利用は避けたかったからです。 何故避けたのかという説明をするために、簡単にgen_eventの話をします。
gen_eventは一つのイベントマネージャーと0個以上のイベントハンドラーで構成され、 マネージャーにメッセージを送信する(notify, sync_notify関数)と、 マネージャに登録したハンドラーの処理が走るという感じのビヘイビアーです。
gen_eventの便利なところは、流したいイベントは同じだけれど別目的でそれぞれ処理をしたいというのが簡単に実装できることが挙げられます。 例えば、ログのコンソール出力、ファイル書き込み、エラーのアラート発報を一つのgen_eventでまとめることができます。
ただ、gen_eventを扱う場合に気をつけなければいけない点があります。 まず、これらの複数ハンドラーは並列実行されない点です。そのため、一つのハンドラーの処理に時間がかかるとハンドラー全体に影響が出ます。 次に、1プロセスにメッセージが集中するので過負荷に弱いです。 事前に設計するイベントマネージャの流量が多いと分かっている場合、 そのマネージャーに処理が大幅に遅れるようなハンドラーを一つだけでも追加するというのは避けたほうが良いでしょう。
fluentdとのやり取りをそのままハンドラーに書き込むと、 レイテンシが大きくなったり、fluentdダウン時などの影響でシステム全体が遅くなる可能性があります。
なので、今回その点を注力したfluentdのクライアントアプリケーションを別ビヘイビアで実装することにしました。
続きを読むErlangのバージョン管理ツール自作
前はkerlを使って複数バージョンを持ってたけど、buildのときのoption指定とかが思う感じに指定できなかったので自前で用意した話。
GitHub - tkyshm/erlch: Erlang version manager
やりたいことは、erlのバージョンを気軽に切り替えられることと、インストールの手間を1コマンドでできるようにすることです。
helpを使うと以下の感じで出てきます。
erlch command list: help: display how to use erlch command. use: switches your erlang version. fetch: caches erlang versions for completions. list: lists your erlang versions. install: install erlang. uninstall: uninstall your installed erlang.
fetchコマンドはzshまたはbashのcompletionファイルを設定してもらった前提で使ってもらうと便利なものとなってます(ほとんど解説ないので結構不親切ですが…)。 fetchするとインストール可能なバージョンをtab補完できるようになります。ちなみに、バージョン情報はgithubのrepoのtagからとってきてます。
補完すると以下な感じです。
~/.erlch ❯❯❯ erlch install OTP -- available versions -- OTP-17.0 OTP-17.5.6.3 OTP-18.2.4.1 OTP-19.1.4 OTP_17.0-rc2 OTP-17.0.1 OTP-17.5.6.4 OTP-18.3 OTP-19.1.5 OTP_R13B03 OTP-17.0.2 OTP-17.5.6.5 OTP-18.3.1 OTP-19.1.6 OTP_R13B04 OTP-17.1 OTP-17.5.6.6 OTP-18.3.2 OTP-19.1.6.1 OTP_R14A OTP-17.1.1 OTP-17.5.6.7 OTP-18.3.3 OTP-19.2 OTP_R14B OTP-17.1.2 OTP-17.5.6.8 OTP-18.3.4 OTP-19.2.1 OTP_R14B01 OTP-17.2 OTP-17.5.6.9 OTP-18.3.4.1 OTP-19.2.2 OTP_R14B02 OTP-17.2.1 OTP-18.0 OTP-18.3.4.2 OTP-19.2.3 OTP_R14B03 OTP-17.2.2 OTP-18.0-rc1 OTP-18.3.4.3 OTP-19.3 OTP_R14B04 OTP-17.3 OTP-18.0-rc2 OTP-18.3.4.4 OTP-19.3.1 OTP_R15A OTP-17.3.1 OTP-18.0.1 OTP-18.3.4.5 OTP-19.3.2 OTP_R15B OTP-17.3.2 OTP-18.0.2 OTP-19.0 OTP-19.3.3 OTP_R15B01 OTP-17.3.3 OTP-18.0.3 OTP-19.0-rc1 OTP-19.3.4 OTP_R15B02 OTP-17.3.4 OTP-18.1 OTP-19.0-rc2 OTP-19.3.5 OTP_R15B03 OTP-17.4 OTP-18.1.1 OTP-19.0.1 OTP-19.3.6 OTP_R15B03-1 OTP-17.4.1 OTP-18.1.2 OTP-19.0.2 OTP-19.3.6.1 OTP_R16A_RELEASE_CANDIDATE OTP-17.5 OTP-18.1.3 OTP-19.0.3 OTP-19.3.6.2 OTP_R16B OTP-17.5.1 OTP-18.1.4 OTP-19.0.4 OTP-20.0 OTP_R16B01 OTP-17.5.2 OTP-18.1.5 OTP-19.0.5 OTP-20.0-rc1 OTP_R16B01_RC1 OTP-17.5.3 OTP-18.2 OTP-19.0.6 OTP-20.0-rc2 OTP_R16B02 OTP-17.5.4 OTP-18.2.1 OTP-19.0.7 OTP-20.0.1 OTP_R16B03 OTP-17.5.5 OTP-18.2.2 OTP-19.1 OTP-20.0.2 OTP_R16B03-1 OTP-17.5.6 OTP-18.2.3 OTP-19.1.1 OTP-20.0.3 OTP_R16B03_yielding_binary_to_term OTP-17.5.6.1 OTP-18.2.4 OTP-19.1.2 OTP-20.0.4 OTP-17.5.6.2 OTP-18.2.4.0.1 OTP-19.1.3 OTP_17.0-rc1 `
$HOME/.erlch
以下はこんな感じ。
~/.erlch ❯❯❯ tree -L 2 . . ├── bin -> /home/tkyshm/.erlch/versions/20.0.4/bin ├── configure_opts ├── repo │ └── otp └── versions ├── 20.0 └── 20.0.4 6 directories, 1 file
useを使ってバージョンを切り替えます。
~/.erlch ❯❯❯ erlch use 20.0 switched erlang version 20.0.
切り替えるとbinのシンボリックリンク先が変更されるだけです。なので、binのpathを通しておきます。
Erlangプロセスのmessage_queue_dataの影響について見てみた
前回の「Erlangのメッセージングでバッファを使う - 備忘録、はじめました。」でバッファがないときに大量のメッセージを送受信すると非常に遅くなることが分かりました。 この劣化の要因は大量のメッセージを一斉に送信していることです。
なので、プロセスのメッセージパッシングの流れについて調べてみました。
- 送信されたメッセージの大きさの計算
- メッセージに対して十分の領域をアロケーション
- メッセージのペイロードをコピー
- メタデータを含めたメッセージの箱(message container)をアロケーション
- “受信プロセスのメッセージキュー”にそのメッセージの箱を挿入
プロセスがメッセージを送受信するときのパフォーマンスは、上記のステップ2が大きく影響を受けます。
ステップ2は、デフォルトではプロセスのロックを取得し、ヒープの空き領域の計算、割当をします。 この処理の間、受信プロセスはメッセージを待ち続けることになります。
erlang 19.0から、ステップ2のアロケーション戦略をプロセス単位で変更できるようになりました。 アロケーション戦略を変更することで、メインへのロックなしにメッセージを送信できるようになるようです。 変更方法は以下の感じです。
process_flag(message_queue_data, on_heap|off_heap)
今回は、この「message_queue_data」について少し調べてみた話です。
続きを読むgo-i18nで必要なjsonをgoのソースコードから生成するgoi18n-parser
モチベーションとして、
- 多言語対応をしたい
- goのソースコードから自動で翻訳ファイルを作成したい
- シンプルでいい(複雑な機能は要らない)
があり、最小限で作ってみたので紹介します。
goi18n-parser
goi18n-parser
ではソースコードの解析に go/ast
を使用しています。
go/ast
を使うと、goのソースコードを抽象構文木にして中身を解析することが出来ます。
今回解析したい内容は、
- 翻訳関数の使われている部分
となります(この「翻訳関数」とは便宜的に書いてるだけです)。 goのソースコード内に翻訳関数が使われていて、その翻訳関数の引数が翻訳対象となるメッセージのIDとして記憶されていきます。
具体的には次のようなコードです:
... fmt.Println(SomeStruct.T("server_error_something")) return fmt.Error(T("server_error_invalid_user_session")) ...
上記の例だと翻訳関数は T
になります。この T
を go/ast
を使って見つけていきます。
今回作成したパッケージ goi18np
を使って取得する場合は次のように書けます:
a := goi18np.Analyzer{} a.AnalyzeFromFile("/path/to/code.go") a.SaveJSON("/path/to/translate.json")
また、複数ファイルも同時に解析できます:
a.AnalyzeFromFiles([]string{"/path/to/code.go", "/path/to/code2.go"})
入力と出力の例を以下に紹介します。
package sample import ( "fmt" "github.com/nicksnyder/go-i18n/i18n" ) type A struct { T i18n.TranslateFunc } func SampleCaller() { T, _ := i18n.Tfunc("eu-US") fmt.Println(T("sample_uniq_key")) for i := 0; i > 100; i++ { if i%10 == 0 { fmt.Println(T("sample_uniq_key_2")) x := T("sample_uniq_key_3") fmt.Println(x) } } T2, _ := i18n.Tfunc("en-US") a := A{T: T2} fmt.Println(a.T("sample_uniq_key_4")) fmt.Printf("%s,%s,%s", a.T("sample_uniq_key_4"), T("sample_uniq_key_5"), T("sample_uniq_key_2")) return }
このソースコードを解析した結果が次のようになります:
[ { "id": "sample_uniq_key", "translation": "" }, { "id": "sample_uniq_key_2", "translation": "" }, { "id": "sample_uniq_key_3", "translation": "" }, { "id": "sample_uniq_key_4", "translation": "" }, { "id": "sample_uniq_key_5", "translation": "" } ]
翻訳内容の部分を one
または other
をつけて、単数形と複数形の文章に分ける処理はしていません。
そこまでの自動化はせずに、必要になったときに人の手で追加する感じになると思います。
また、実際にjsonで保存するときは go1.8
で出てきた sort.Slice
を使うなどして、ID順にソートして差分が少なくなるように工夫が必要かと思います。