koukiblog

たぶんweb系の話題

PhoenixでHTTP Headerを利用した認証を行う

PhoenixAPIを実装するときに、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つらしい。

iOS
github.com


Android
github.com

C#
github.com

emberのfastbootについて調べた

前からちょっと気になっていたのだけど、時間がとれたので調べてみました。
↓の動画で説明されてます。
vimeo.com

fastbootとは、emberが用意しているSSR(サーバーサイドレンダリング)の仕組みで、nodejsでemberアプリを動かしている。

つまり、サーバサイドアプリとemberアプリはそれぞれ独立しているのが前提。
たとえば、Railsでwebアプリを作っていた場合、fastbootを利用するには、unicornなどのアプリケーションサーバとは別にnodejsが必要です。

jQueryなど、ブラウザのAPIに依存した処理があると、当然エラーになります。($.ajax('xxxx')みたいな)
ember-networkのようにemberがplolyfilを用意していて、ここは開発者が気を付けて開発する必要あり。

あと、クライアントMVCSSRで気になるのは、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を継承してるって知ってれば一瞬で解決できるんだろうけど、なかなか難しい

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は別で切り出すとかありそうだし。