Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ࣗݾ঺հ • twitter: @sgwr_dts • github/bitbicket: winebarrel • ܙൺणͷձࣾͰΠϯϑϥΤϯδχΞΛ΍ͬͯ·͢ • ීஈ͸Rubyͱ͔AWSͱ͔࢖ͬͯ·͢

Slide 3

Slide 3 text

ຊ೔ͷαϯϓϧίʔυ https:/ /github.com/winebarrel/ elixir_plug_examples git clone ... cd elixir_plug_examples mix deps.get mix run --no-halt -e ElixirPlugExamples.HttpPlug.start

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Plugͱ͸ • Python: WSGI • Ruby: Rack • Perl: PSGI/Plack • Node.js: ExpressͷMiddleware

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

PlugͷϞδϡʔϧ Plug.Static Plug.Test Plug.Upload

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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]

Slide 13

Slide 13 text

Plug.Adapters.Translator • PlugͷಛఆͷΤϥʔϝοηʔδͷϑΥʔϚοτΛมߋ • ϥΠϒϥϦ಺Ͱར༻ Logger.error ['Ranch listener XXX', [" ref", " protocol", " pid", " reason"]]

Slide 14

Slide 14 text

Plug.Builder • plugͱ͍͏ϚΫϩΛ௥Ճ • RackͰ͍͏ͱ͜ΖͷuseΛఆٛ • ಺෦ͰPlug.ConnΛΠϯϙʔτ • Ϟδϡʔϧ֎ͷؔ਺΋ݺͼग़ͤΔ import AnotherModule, only: [interesting_plug: 2] plug :interesting_plug

Slide 15

Slide 15 text

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 #(ଓ͘)

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Plug.CSRFProtection • CSRFରࡦͷτʔΫϯੜ੒ͱνΣοΫ

Slide 18

Slide 18 text

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])

Slide 19

Slide 19 text

Plug.Conn • ίωΫγϣϯ·ΘΓͷجຊతͳػೳͷϞδϡʔϧ • ϦΫΤετ͔Βऔಘ: get_xxx(conn, ...) • Ϩεϙϯεʹ௥Ճ: put_xxx(conn, ...) • ͦͷଞηογϣϯૢ࡞ͨ͠ΓɺϢʔβσʔλΛૢ࡞ͨ͠Γ… def call(conn, _opts) do ... end

Slide 20

Slide 20 text

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: "", #(ଓ͘)

Slide 21

Slide 21 text

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}

Slide 22

Slide 22 text

Plug.Conn / misc • Adapter: ΞμϓλͷϏϔΠϏΞ • Cookies:ΫοΩʔศརؔ਺ • Query: ΫΤϦʔετϦϯάศརؔ਺ • Conn.Status: εςʔλείʔυศརؔ਺ • Unfetched:ύϥϝʔλ΍ϘσΟʔ͕ͳ͍ͱ͖ʹϚοϓ͞ΕΔ஋ • Utils: ίωΫγϣϯؔ࿈ϢʔςΟϦςΟ

Slide 23

Slide 23 text

Plug.Crypto • ҉߸Խ΍ϋογϡΩʔͷੜ੒ɾൺֱͳͲͰ࢖ΘΕΔ಺෦ϥΠϒ ϥϦ • KeyGenerator: ύεϫʔυͷϋογϡੜ੒ͳͲ • MessageEncryptor: ϝοηʔδͷ҉߸ԽͳͲ • MessageVerifier: ϝοηʔδ΁ͷॺ໊ɾݕࠪͳͲ

Slide 24

Slide 24 text

Plug.Debugger • not a plugͱͷ͜ͱ • Τϥʔ͕ൃੜͨ͠ͱ͖ʹσόοάը໘Λදࣔ͢Δ

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Plug.ErrorHandler • จࣈ௨ΓΤϥʔϋϯυϦϯά͢Δplug

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Plug.HTML • HTMLͷΤεέʔϓͷؔ਺͚ͩఆٛ Plug.HTML.html_escape("")

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Plug.Logger • ΞΫηεϩάΛग़ྗ plug Plug.Logger, log: :debug 22:32:24.232 [debug] GET / 22:32:24.232 [debug] Sent 200 in 12µs

Slide 32

Slide 32 text

Plug.MIME • MIMEͷ௥Ճ • MIMEؔ࿈ͷศརؔ਺ config :plug, :mimes, %{ "application/vnd.api+json" => ["json-api"] }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Plug.MethodOverride • _method=PUTΈ͍ͨͳύϥϝʔλΛ͚ͭͯϝιουΛม͑Δ • RailsͷresourcesͷDELETEͱ͔Ͱ࢖ΘΕΔ΍ͭ

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Plug.Parsers • ϦΫΤετϘσΟͷύʔα • ҎԼʹରԠ • JSON • MULTIPART • URLENCODED

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Plug.RequestId • ϦΫΤετIDΛ͚ͭΔ

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Plug.Router • ϧʔςΟϯάͷDSLΛఆٛͰ͖ΔΑ͏ʹ͢Δ • globͷಈ࡞͕حົͳײ͡

Slide 41

Slide 41 text

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 #(ଓ͘)

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Plug.SSL • https΁ͷϦμΠϨΫτͳͲͷಈ࡞Λߦ͏ • :rewrite_onɺ:hstsɺ:expiresɺ:subdomainsɺ:host plug Plug.SSL, rewrite_on: [:x_forwarded_proto]

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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])

Slide 46

Slide 46 text

Plug.Static • ੩తϑΝΠϧͷ഑৴ػೳΛఏڙ

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Plug.Test • ςετͷͨΊͷศརϚΫϩΛఆٛ

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Plug.Upload • Ξοϓϩʔυͨ͠ϑΝΠϧͷॲཧ • ผϓϩηεͰಈ͘

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Ҏ্Ͱ͢ ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠