リバースプロキシ(Nginx)がToo Many Redirects
おはようございます。
SSLの終端はNginxで、その後ろにWebアプリがいる、みたいなよくある構成をやっていたら、「リダイレクトが多すぎます」というようなエラーが出ました。前にも同じような構成をつくったことがあったので、こんなところでハマるとは、という感じでしたが、設定ファイルから1行抜けているのが原因でした。ポカミス。
upstream app { server 127.0.0.1:4000; } # For WebSocket map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 80; listen [::]:80; server_name MYDOMAIN.COM; return 301 https://$host$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name MYDOMAIN.COM; ssl_certificate /etc/letsencrypt/live/MYDOMAIN.COM/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/MYDOMAIN.COM/privkey.pem; location / { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; # これ忘れてた proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://app; } }
http → https → https → https → ... というリダイレクトがされていたので、443の設定がトチってるんじゃのーと調べていたら見つかったわけです。
/socket
へのリクエストのときにしか必要ない設定もあるので、その辺を整理して、あとSSLのランクみたいなの測るやつでAくらいにしときます。
追記
ホントに原因これなのかなという気がしてきた。合わせて使ってるcloudflareの設定が疑わしい。
PhoenixでモデルのSchemaに無い属性をレスポンスのJSONに含める
はいっこんばんは。
PhoenixでJSON APIを書いてて迷ったことがあったので書きます。見落としている簡単なやり方があるんじゃないかと。
前提
User
モデルとPost
モデルがあり、User
はPost
をlikeできるとします。posts
テーブルのカラムはid
とtitle
とbody
があり、likes
テーブルにはid
とuser_id
とpost_id
があるとします。タイムスタンプは省きます。
defmodule User do use App.Web, :model schema "users" do field :name, :string has_many :likes, Like end end defmodule Post do use App.Web, :model schema "posts" do field :title, :string field :body, :text has_many :likes, Like end end defmodule Like do use App.Web, :model schema "likes" do belongs_to :user, User belongs_to :post, Post end end
Postの一覧にLike済みかを含める
GET /posts
されたら何を返すかという話なのですが、schemaにある属性をそのまま返すと以下のようになります。
{ "posts": [ {"id": 1, "title": "Hello", "body": "Lorem ipsum"}, ... ] }
post_view.ex
はこんな感じ。
defmodule PostView do use App.Web, :view def render("index.json", %{posts: posts} do %{posts: render_many(posts, PostView, "post.json")} end def render("show.json", %{post: post} do %{posts: render_one(post, PostView, "post.json")} end def render("post.json", %{post: post}) do %{id: post.id, title: post.title, body: post.body} end end
ここで、自分がLike済みか(liked
)をここに含めたいと考えたとき、どうするでしょうか。リクエストヘッダにあるトークンからcurrent_user
を判定しているものとして、以下のようにしたいと。
{ "posts": [ {"id": 1, "title": "Hello", "body": "Lorem ipsum", "liked": true}, ... ] }
EctoでDBから取得
そもそも始めに、EctoでPost
を取ってくるときに、一緒にlike済みかも取得するにはどうすればいいでしょうか。多分こんな。
Post |> join(:left, [p], l in Like, l.post_id == p.id and l.user_id == ^current_user.id) |> select([p, l], %{post: p, liked: not is_nil(l.id)})
Repo.all
すると、以下の様なMapのリストが返ってきます。
[%{post: %Post{...}, liked: true}, %{post: %Post{...}, liked: false}, ... ]
ControllerからViewへ
mix phoenix.gen.json
とかすると、Controllerには以下のようなコードが生成されます。
render(conn, "index.json", posts: posts)
すると上に書いたViewで
%{posts: render_many(posts, PostView, "post.json")}
の結果がJSONにされてレスポンスとなります。render_many/4
のドキュメントによると、以下の2つがザックリ同じだそうです。
render_many(posts, PostView, "post.json") Enum.map posts, fn post -> render(PostView, "show.json", post: post) end
render/3
の結果はPostView.render("show.json", post: post)
となります。余談ですが、render/3
の第3引数のキーである:post
はPostView.__resource__
から来ていて、こちらで指定する場合はrender_many(posts, PostView, "post.json", as: :foo)
と書きます。
このままのコードでは、レスポンスJSONの内容に、post.*
しか入れることができません。つまり、posts
スキーマに含まれていないliked
を入れることができません。ではどこにliked
を入れ込んでいけるか辿っていくと、Controllerのrender(conn, "index.json", posts: posts)
の第3引数まで遡ります。ここにposts
しか渡していないためliked
が返せないということになります。ではEctoでDBから取ってきたこれらをまとめて雑にdata
としてぶち込みます。
# data = [%{post: %Post{...}, liked: true}, %{post: %Post{...}, liked: false}, ... ] render(conn, "index.json", data: data)
Viewを変更
Controllerでrender/3
に与える引数を変えたのでViewも修正する必要があります。
defmodule PostView do use App.Web, :view def render("index.json", %{data: data} do %{posts: render_many(data, PostView, "post.json", as: :data)} end def render("show.json", %{data: data} do %{posts: render_one(data, PostView, "post.json", as: :data)} end def render("post.json", %{data: %{post: post, liked: liked}}) do %{id: post.id, title: post.title, body: post.body, liked: liked} end end
これでなんとかliked
が入り、当初の希望通りのJSONが返せます。
所感
ここまでやって、MVCの中を駆けまわっていたり、結合が密な感じがして、なんか面倒くさくないですか、という気持ちがちょっとだけ。mix
タスクで生成したコードから出発してるからそう感じるだけかもしれませんし、Viewの中でもっと関数定義してパターンマッチすれば、いくつもliked
みたいなものが増えてもそれほど煩雑にならないのかもしれませんが。あとEctoで取ってきたものをそのままViewに投げているので、依存しているように見えてるのかもしませんね。ただ、なんか見落としてんじゃねーかなと思う午前1時半。
はやくElixirConf 2016の録画が観たいという気持ち
もう時間は深夜1時を回っていて寝なきゃいけないのに、こんなツイートを見てウォーッ!!となってしまった。
Watch this space for #ElixirConf videos. All should be out by Sep 26.https://t.co/I2OsL8TQFW
— ElixirConf (@ElixirConf) 2016年9月8日
でもまだ1本もアップされておらずしゅんとしていたらこんなまとめがあるじゃないか。
寝なきゃいけないのに。