Elixir Meetup #2 Plugのモジュールを一通り使ってみた

Elixir Meetup #2 Plugのモジュールを一通り使ってみた

F811665799e1139b63140b72f3e8631f?s=128

Genki Sugawara

March 21, 2016
Tweet

Transcript

  1. PlugͷϞδϡʔϧΛ Ұ௨Γ࢖ͬͯΈͨ Genki Sugawara Elixir Meetup #2 in Drecom

  2. ࣗݾ঺հ • twitter: @sgwr_dts • github/bitbicket: winebarrel • ܙൺणͷձࣾͰΠϯϑϥΤϯδχΞΛ΍ͬͯ·͢ •

    ීஈ͸Rubyͱ͔AWSͱ͔࢖ͬͯ·͢
  3. ຊ೔ͷαϯϓϧίʔυ https:/ /github.com/winebarrel/ elixir_plug_examples git clone ... cd elixir_plug_examples mix

    deps.get mix run --no-halt -e ElixirPlugExamples.HttpPlug.start
  4. Plugͱ͸ https:/ /hexdocs.pm/plug/ Plug is: A specification for composable modules

    between web applications Connection adapters for different web servers in the Erlang VM
  5. Plugͱ͸ • Python: WSGI • Ruby: Rack • Perl: PSGI/Plack

    • Node.js: ExpressͷMiddleware
  6. PlugͷϞδϡʔϧ Plug.Adapters.Cowboy Plug.Adapters.Translator Plug.Builder Plug.CSRFProtection Plug.Conn Plug.Conn.Adapter Plug.Conn.Cookies Plug.Conn.Query Plug.Conn.Status

    Plug.Conn.Unfetched Plug.Conn.Utils
  7. PlugͷϞδϡʔϧ Plug.Crypto Plug.Crypto.KeyGenerator Plug.Crypto.MessageEncryptor Plug.Crypto.MessageVerifier Plug.Debugger Plug.ErrorHandler Plug.HTML Plug.Head Plug.Logger

    Plug.MIME Plug.MethodOverride
  8. PlugͷϞδϡʔϧ Plug.Parsers Plug.Parsers.JSON Plug.Parsers.MULTIPART Plug.Parsers.URLENCODED Plug.RequestId Plug.Router Plug.SSL Plug.Session Plug.Session.COOKIE

    Plug.Session.ETS Plug.Session.Store
  9. PlugͷϞδϡʔϧ Plug.Static Plug.Test Plug.Upload

  10. Plug.Adapters.Cowboy • CowboyͷΞμϓλ • ͍·ͷͱ͜Ζ͜Ε͚ͩ

  11. Plug.Adapters.Cowboy defmodule ElixirPlugExamples.HttpPlug do import Plug.Conn def init(opts), do: opts

    def call(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "Hello world") end end Plug.Adapters.Cowboy.http ElixirPlugExamples.HttpPlug, [], port: 9001
  12. Plug.Adapters.Cowboy defmodule ElixirPlugExamples.HttpsPlug do import Plug.Conn def init(opts), do: opts

    def call(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "Hello world") end end Plug.Adapters.Cowboy.https ElixirPlugExamples.HttpsPlug, [], [ keyfile: Path.join([__DIR__, "server.key"]), certfile: Path.join([__DIR__, "server.crt"]), port: 9001]
  13. Plug.Adapters.Translator • PlugͷಛఆͷΤϥʔϝοηʔδͷϑΥʔϚοτΛมߋ • ϥΠϒϥϦ಺Ͱར༻ Logger.error ['Ranch listener XXX', ["

    ref", " protocol", " pid", " reason"]]
  14. Plug.Builder • plugͱ͍͏ϚΫϩΛ௥Ճ • RackͰ͍͏ͱ͜ΖͷuseΛఆٛ • ಺෦ͰPlug.ConnΛΠϯϙʔτ • Ϟδϡʔϧ֎ͷؔ਺΋ݺͼग़ͤΔ import

    AnotherModule, only: [interesting_plug: 2] plug :interesting_plug
  15. Plug.Builder defmodule ElixirPlugExamples.BuilderPlug do use Plug.Builder plug :hello plug :world

    plug :append, "Zzz" plug :resp def hello(conn, _opts) do conn |> assign(:msg, "hello") # stop response #conn |>send_resp(200, "stopped") |> halt end #(ଓ͘)
  16. Plug.Builder #(ଓ͖) def world(conn, _opts) do msg = conn.assigns[:msg] <>

    " world" conn |> assign(:msg, msg) end def append(conn, opts) do msg = conn.assigns[:msg] <> opts conn |> assign(:msg, msg) end def resp(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, conn.assigns[:msg]) end end
  17. Plug.CSRFProtection • CSRFରࡦͷτʔΫϯੜ੒ͱνΣοΫ

  18. Plug.CSRFProtection defmodule ElixirPlugExamples.CSRFProtectionPlug do use Plug.Builder plug Plug.Session, store: :ets,

    key: "_my_app_session", table: :session plug :fetch_session plug Plug.CSRFProtection plug :resp def resp(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, Plug.CSRFProtection.get_csrf_token) end end :ets.new(:session, [:named_table, :public, read_concurrency: true])
  19. Plug.Conn • ίωΫγϣϯ·ΘΓͷجຊతͳػೳͷϞδϡʔϧ • ϦΫΤετ͔Βऔಘ: get_xxx(conn, ...) • Ϩεϙϯεʹ௥Ճ: put_xxx(conn,

    ...) • ͦͷଞηογϣϯૢ࡞ͨ͠ΓɺϢʔβσʔλΛૢ࡞ͨ͠Γ… def call(conn, _opts) do ... end
  20. Plug.Conn %Plug.Conn{ adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [], body_params:

    %Plug.Conn.Unfetched{aspect: :body_params}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "GET", owner: #PID<0.248.0>, params: %Plug.Conn.Unfetched{aspect: :params}, path_info: [], peer: {{127, 0, 0, 1}, 56143}, port: 9001, private: %{}, query_params: %Plug.Conn.Unfetched{aspect: :query_params}, query_string: "", #(ଓ͘)
  21. Plug.Conn #(ଓ͖) remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies},

    req_headers: [ {"host", "localhost:9001"}, {"user-agent", "curl/7.43.0"}, {"accept", "*/*"}], request_path: "/", resp_body: nil, resp_cookies: %{}, resp_headers: [ {"cache-control","max-age=0, private, must-revalidate"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil}
  22. Plug.Conn / misc • Adapter: ΞμϓλͷϏϔΠϏΞ • Cookies:ΫοΩʔศརؔ਺ • Query:

    ΫΤϦʔετϦϯάศརؔ਺ • Conn.Status: εςʔλείʔυศརؔ਺ • Unfetched:ύϥϝʔλ΍ϘσΟʔ͕ͳ͍ͱ͖ʹϚοϓ͞ΕΔ஋ • Utils: ίωΫγϣϯؔ࿈ϢʔςΟϦςΟ
  23. Plug.Crypto • ҉߸Խ΍ϋογϡΩʔͷੜ੒ɾൺֱͳͲͰ࢖ΘΕΔ಺෦ϥΠϒ ϥϦ • KeyGenerator: ύεϫʔυͷϋογϡੜ੒ͳͲ • MessageEncryptor: ϝοηʔδͷ҉߸ԽͳͲ

    • MessageVerifier: ϝοηʔδ΁ͷॺ໊ɾݕࠪͳͲ
  24. Plug.Debugger • not a plugͱͷ͜ͱ • Τϥʔ͕ൃੜͨ͠ͱ͖ʹσόοάը໘Λදࣔ͢Δ

  25. Plug.Debugger defmodule ElixirPlugExamples.DebuggerPlug do import Plug.Conn use Plug.Debugger def init(opts),

    do: opts def call(conn, _opts) do raise "oops" end end
  26. Plug.ErrorHandler • จࣈ௨ΓΤϥʔϋϯυϦϯά͢Δplug

  27. Plug.ErrorHandler defmodule ElixirPlugExamples.ErrorHandlerPlug do import Plug.Conn use Plug.ErrorHandler def init(opts),

    do: opts def call(_conn, _opts) do raise "oops" end defp handle_errors(conn, err) do send_resp(conn, conn.status, inspect(err)) end end
  28. Plug.HTML • HTMLͷΤεέʔϓͷؔ਺͚ͩఆٛ Plug.HTML.html_escape("<foo>")

  29. Plug.Head • HEADΛGETʹม׵ • ༻్͕Α͘Θ͔Βͳ͍…

  30. Plug.Head defmodule ElixirPlugExamples.HeadPlug do use Plug.Builder plug Plug.Head plug :resp

    def resp(conn, _opts) do IO.inspect conn.method #=> GET conn |> put_resp_content_type("text/plain") |> send_resp(200, "") end end curl -XHEAD http://localhost:9001
  31. Plug.Logger • ΞΫηεϩάΛग़ྗ plug Plug.Logger, log: :debug 22:32:24.232 [debug] GET

    / 22:32:24.232 [debug] Sent 200 in 12µs
  32. Plug.MIME • MIMEͷ௥Ճ • MIMEؔ࿈ͷศརؔ਺ config :plug, :mimes, %{ "application/vnd.api+json"

    => ["json-api"] }
  33. Plug.MIME IO.inspect Plug.MIME.extensions("text/html") #=> ["html", "htm"] IO.inspect Plug.MIME.extensions("application/json") #=> ["json"]

    IO.inspect Plug.MIME.extensions("foo/bar") #=> [] IO.inspect Plug.MIME.path("index.html") #=> "text/html" IO.inspect Plug.MIME.type("txt") #=> "text/plain" IO.inspect Plug.MIME.type("foobarbaz") #=> "application/octet-stream" IO.inspect Plug.MIME.valid?("text/plain") #=> true IO.inspect Plug.MIME.valid?("foo/bar") #=> false
  34. Plug.MethodOverride • _method=PUTΈ͍ͨͳύϥϝʔλΛ͚ͭͯϝιουΛม͑Δ • RailsͷresourcesͷDELETEͱ͔Ͱ࢖ΘΕΔ΍ͭ

  35. Plug.MethodOverride defmodule ElixirPlugExamples.MethodOverridePlug do use Plug.Builder plug Plug.Parsers, parsers: [:urlencoded,

    :multipart] plug Plug.MethodOverride plug :resp def resp(%Plug.Conn{method: "PUT"} = conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "PUT") end end curl -d _method=PUT http://localhost:9001
  36. Plug.Parsers • ϦΫΤετϘσΟͷύʔα • ҎԼʹରԠ • JSON • MULTIPART •

    URLENCODED
  37. Plug.Parsers defmodule ElixirPlugExamples.ParsersPlug do use Plug.Builder plug Plug.Parsers, parsers: [:urlencoded,

    :multipart, :json], json_decoder: Poison # required plug :resp def resp(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, inspect(conn.body_params)) end end curl -H "Content-type: application/json" -X POST \ -d '{"user":{"first_name":"firstname"}}' localhost:9001
  38. Plug.RequestId • ϦΫΤετIDΛ͚ͭΔ

  39. Plug.RequestId defmodule ElixirPlugExamples.RequestIdPlug do use Plug.Builder plug Plug.RequestId plug :resp

    def resp(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, get_resp_header(conn, "x-request-id")) #=> "emv6tkni35sbvl38udbc4qvfhalgvfpj" end end
  40. Plug.Router • ϧʔςΟϯάͷDSLΛఆٛͰ͖ΔΑ͏ʹ͢Δ • globͷಈ࡞͕حົͳײ͡

  41. Plug.Router defmodule ElixirPlugExamples.RouterPlug do use Plug.Router plug :match plug :dispatch

    get "/hello" do send_resp(conn, 200, "world") end get "/hello/r*glob" do send_resp(conn, 200, "route after /hello: #{inspect glob}") end get "/hello/:name" do send_resp(conn, 200, "hello #{name}") end #(ଓ͘)
  42. Plug.Router #(ଓ͖) match "/foo/:bar" when byte_size(bar) <= 2, via: :get

    do send_resp(conn, 200, "hello world #{inspect(bar)}") end match "/foo/bar", via: :get do send_resp(conn, 200, "hello world") end #match ["foo", bar], via: :get do # send_resp(conn, 200, "hello world") #end match _ do send_resp(conn, 404, "oops") end end
  43. Plug.SSL • https΁ͷϦμΠϨΫτͳͲͷಈ࡞Λߦ͏ • :rewrite_onɺ:hstsɺ:expiresɺ:subdomainsɺ:host plug Plug.SSL, rewrite_on: [:x_forwarded_proto]

  44. Plug.Session • ηογϣϯػೳΛఏڙ • etsͱcookieʹରԠ • cookie΋ets࢖ͬͯΔͬΆ͍ײ͡ • memcached΋͋ͬͨ •

    gutschilla/plug-session-memcached
  45. Plug.Session defmodule ElixirPlugExamples.SessionPlug do use Plug.Builder plug Plug.Session, store: :ets,

    key: "_my_app_session", table: :session plug :fetch_session # required plug :resp def resp(conn, _opts) do count = get_session(conn, :counter) || 0 conn |> put_session(:counter, count + 1) |> put_resp_content_type("text/plain") |> send_resp(200, Integer.to_string(count)) end end :ets.new(:session, [:named_table, :public, read_concurrency: true])
  46. Plug.Static • ੩తϑΝΠϧͷ഑৴ػೳΛఏڙ

  47. Plug.Static defmodule ElixirPlugExamples.StaticPlug do use Plug.Builder plug Plug.Static, at: "/public",

    from: __DIR__ plug :not_found def not_found(conn, _) do send_resp(conn, 404, "not found") end end
  48. Plug.Test • ςετͷͨΊͷศརϚΫϩΛఆٛ

  49. Plug.Test defmodule ElixirPlugExamplesTest do use ExUnit.Case, async: true use Plug.Test

    alias ElixirPlugExamples.HttpPlug test "puts session cookie" do conn = conn(:get, "/") conn = ElixirPlugExamples.HttpPlug.call(conn, []) assert conn.resp_body == "xHello world" end end
  50. Plug.Upload • Ξοϓϩʔυͨ͠ϑΝΠϧͷॲཧ • ผϓϩηεͰಈ͘

  51. Plug.Upload defmodule ElixirPlugExamples.UploadPlug do use Plug.Builder plug Plug.Parsers, parsers: [:urlencoded,

    :multipart] plug :resp def resp(conn, _opts) do case conn.params do %{"file" => file} -> IO.inspect file _ -> IO.puts "'file' key not found" end conn |> put_resp_content_type("text/plain") |> send_resp(200, "OK") end end Plug.Upload.start_link
  52. Plugͷࣗ࡞ defmodule JSONHeaderPlug do @behaviour Plug import Plug.Conn def init(opts)

    do opts end def call(conn, _opts) do conn |> put_resp_content_type("application/json") end end
  53. Ҏ্Ͱ͢ ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠