PhoenixでHTTP Headerを利用した認証を行う
PhoenixでAPIを実装するときに、http headerを利用して簡易な認証を行う時の方法。
http headerで認証を行うplugを作成します。
/web/plugs/api_auth.ex
defmodule App.Plug.APIAuth do import Plug.Conn def init(default), do: default def call(conn, auth_header) do {key, value} = auth_header header = Enum.find(conn.req_headers, &elem(&1, 0) == key) if header && elem(header, 1) == value do conn else send400 conn end end defp send400(conn) do conn |> put_resp_content_type("text/plain") |> send_resp(400, "Bad Request") |> halt end end
Phoenixの場合、http headerはconn.req_headersで取得できるのでそれを利用します。
{key,value}のtupleのリストになっています。
Plug.Conn – Plug v1.1.6
作成したPlugをRouterに設定すれば完了です。
web/router.ex
pipeline :api do plug :accepts, ["json"] plug App.Plug.APIAuth, {"x-sample-key", "xxxxx"} end
環境毎に変更する場合は、configに追記して、Application.get_env で取得すればok
phoenix以外からphoenixのchannelを利用する
Rails + node.js(+socket.io) で作ったアプリの、node.jsの部分をphoenixに置き換えてみたときに必要だった。
phoenixのchannelのclientは
github.com
にあり、このファイルを利用すればよい。
es6で書かれているが、他のファイルに依存はしていないので、babelでコンパイルしたものを配置すればok。
phoenix.jsを読み込んでしまえば、phoenixのチュートリアルにあるように
var phoenix = require("phoenix"); socket = new phoenix.Socket("/socket", {params: {token: window.userToken}});
のようにして利用できる。
別のwebアプリケーションからphoenixのchannel使いたいケースって結構ありそうだけど、現状はこれしか方法なさそう。
(とはいえ、socket.ioほど分厚い仕様があるわけではないので、client自作してしまうのあり)
ちなみに、他の言語のクライアントは現在以下の3つらしい。
emberのfastbootについて調べた
前からちょっと気になっていたのだけど、時間がとれたので調べてみました。
↓の動画で説明されてます。
vimeo.com
fastbootとは、emberが用意しているSSR(サーバーサイドレンダリング)の仕組みで、nodejsでemberアプリを動かしている。
つまり、サーバサイドアプリとemberアプリはそれぞれ独立しているのが前提。
たとえば、Railsでwebアプリを作っていた場合、fastbootを利用するには、unicornなどのアプリケーションサーバとは別にnodejsが必要です。
jQueryなど、ブラウザのAPIに依存した処理があると、当然エラーになります。($.ajax('xxxx')みたいな)
ember-networkのようにemberがplolyfilを用意していて、ここは開発者が気を付けて開発する必要あり。
あと、クライアントMVCのSSRで気になるのは、DOMの生成ですが、
emberの場合は、htmlbarsというemberで利用しているテンプレートエンジンがhtmlのサブセットを理解できて、それを利用しています。
HTMLBars: The Next-Generation of Templating in Ember.js
手元で確認してみたところ
<div> <span>welcome to ember</span> <div> {{outlet}} </div>
がfastboot用にビルドを行ったところ
buildFragment: function buildFragment(dom) { var el0 = dom.createDocumentFragment(); var el1 = dom.createElement("div"); var el2 = dom.createTextNode("\n"); dom.appendChild(el1, el2); var el2 = dom.createElement("span"); var el3 = dom.createTextNode("welcome to ember"); dom.appendChild(el2, el3); dom.appendChild(el1, el2); var el2 = dom.createTextNode("\n"); dom.appendChild(el1, el2); var el2 = dom.createElement("div"); var el3 = dom.createTextNode("\n "); dom.appendChild(el2, el3); var el3 = dom.createComment(""); dom.appendChild(el2, el3); var el3 = dom.createTextNode("\n"); dom.appendChild(el2, el3); dom.appendChild(el1, el2); var el2 = dom.createTextNode("\n\n"); dom.appendChild(el1, el2); dom.appendChild(el0, el1); var el1 = dom.createTextNode("\n"); dom.appendChild(el0, el1); return el0; }
に変換されていました。
ReactとJSXの関係に近いですね。JSXは結構制約が強いイメージだったけど、htmlbarsは自由度高そうな雰囲気。
webのフロントエンドは日々複雑になっているし、スマホアプリにAPIを提供するみたいに、サーバーサイドアプリはAPIに徹して、webのフロントエンドはクライアントMVC(SSR出来ればなおよし)で構築するのもよいんじゃないかなという気もしてきました。
emberそんなに悪くないと思うんだけど、日本語リソースが少ないのが気になるところ。
複雑なwebアプリ作るときには結構現実的な選択肢だと思うんですけどね。
Railsのforce_sslの判定方法
Railsのforce_sslがどうやってSSLかどうか見分けてるかわからなくて調べたときのメモ
https://github.com/rails/rails/blob/3d70f0740b26b0a137d7e6436f9909330f8ee888/actionpack/lib/action_controller/metal/force_ssl.rb#L76
ここまでは簡単に辿り着けるんだけど、このrequest.ssl? が何をしているのかがわからない。。
Diving in Rails - The request handling
rails request soruceとかでぐぐって見つけた記事を読むと
requestが、ActionDispatch::Requestなことがわかる
rails/request.rb at e595d91ac2c07371b441f8b04781e7c03ac44135 · rails/rails · GitHub
ActionDispatch::Reqeustを見てみる。けど、ここにもssl? はいない。けどRack::Requestを継承していることがわかる。
rack/request.rb at master · rack/rack · GitHub
Rack::Requestを見てみると、やっとssl? を発見。
if @env[HTTPS] == 'on'
'https'
elsif @env[HTTP_X_FORWARDED_SSL] == 'on'
'https'
elsif @env[HTTP_X_FORWARDED_SCHEME]
@env[HTTP_X_FORWARDED_SCHEME]
elsif @env[HTTP_X_FORWARDED_PROTO]
@env[HTTP_X_FORWARDED_PROTO].split(',')[0]
else
@env["rack.url_scheme"]
end
最終的な条件式はこれでした
Controllerから参照できるrequestは、AcitonDispatch::RequestでそれはRack::Requestを継承してるって知ってれば一瞬で解決できるんだろうけど、なかなか難しい
バージョン指定してgemを実行
gem _#{バージョン}_ rails
とすればよかった
再開したい
気づいたら1年放置してた。。
手元のメモ帳に書いて捨てるくせがなかなか消えないなー
Asset PipelineのPreCompile対象とその設定
いつも忘れては調べて思い出して。。を繰り返していたのでいいかげん覚えようと思ってメモっておく
- precompile対象のファイル
デフォルトでは
JS:
app/assets/application.js
CSS:
app/assets/application.css
となっている。
つまり、デフォルトの設定は、アプリケーションでjs,css共に1ファイルになるべきという考え方になっていて
どの画面でもコンパイル後のapplication.js,application.cssで動くようにしないといけない。
( // require_tree . がついてるので、コントローラー毎のJSを1ファイルに結合して使用する形になる)
気をつけないといけないのが、特定の画面で、特定のJSだけを読み込もうとして
<%= javascript_include_tag params[:controller] %>
みたいに書いていた場合、
production環境で実行すると、下記のようなエラーが発生して、レンダリングすら行われない
ActionView::Template::Error (XXXXXXX.js isn't precompiled): 40: <!-- Javascripts 41: ================================================== --> 42: <!-- Placed at the end of the document so the pages load faster --> 43: <%= javascript_include_tag params[:controller] %> 44: </body> 45: </html>
前述の通り、コンパイルされるのは、application.jsだけなので、production環境では、ファイルが見つからずエラーになる。
- precompile対象の設定
設定を追加することで、precompile対象を追加することができる
/config/enviroments/production.rb内の
config.assets.precompile
に追加でおk。
基本的には、JSは1ファイルにまとめても動くように作って、必要に応じて追加するのがよいのかな。
管理画面用のJSは別で切り出すとかありそうだし。