Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rewriting Rack: A Functional Approach

Alex Wheeler
November 15, 2017

Rewriting Rack: A Functional Approach

RubyConf 2017

Alex Wheeler

November 15, 2017
Tweet

More Decks by Alex Wheeler

Other Decks in Technology

Transcript

  1. 100.class #=> Integer 0x1a.class #=> Integer nil.class #=> NilClass true.class

    #=> TrueClass 'foo'.class #=> String 0b11010.class #=> Integer 123.4.class #=> Float 1.234e2.class #=> Float
  2. p = proc { |x| x + 1 } p.call(1)

    p[1] p.(1) p.send(:call, 1)
  3. p = proc { |x| x + 1 } p.call(1)

    p[1] p.(1) p.send(:call, 1) p.public_send(:call, 1)
  4. p = proc { |x| x + 1 } p.call(1,

    2, 3, 4, 5, 6) p[1, “msg”, :k] p.(1, :b, :c, “msg”) p.send(:call, 1, :x, :y, :z) p.public_send(:call, 1, 2, 3)
  5. require 'rack' class App def call(env) ['200', {'Content-Type' => 'text/html'},

    ['<p>Hi</p>']] end end app = App.new Rack::Handler::Unicorn
  6. require 'rack' class App def call(env) ['200', {'Content-Type' => 'text/html'},

    ['<p>Hi</p>']] end end app = App.new Rack::Handler::Unicorn.run(app)
  7. require 'rack' class App def call(env) ['200', {'Content-Type' => 'text/html'},

    ['<p>Hi</p>']] end end app = App.new Rack::Handler::Puma.run(app)
  8. require 'rack' class App def call(env) ['200', {'Content-Type' => 'text/html'},

    ['<p>Hi</p>']] end end app = App.new Rack::Handler::WEBrick.run(app)
  9. app = ->(env){[‘200’, {'Content-Type' => 'text/html'}, ['<p>Hi</p>']]} builder = Rack::Builder.new

    builder.use(Logger) builder.run(app) Rack::Handler::WEBrick.run(builder)
  10. app = ->(env){[‘200’, {'Content-Type' => 'text/html'}, ['<p>Hi</p>']]} builder = Rack::Builder.new

    builder.use(Logger) builder.use(Cache) builder.run(app) Rack::Handler::WEBrick.run(builder)
  11. app = ->(env){[‘200’, {'Content-Type' => 'text/html'}, ['<p>Hi</p>']]} builder = Rack::Builder.new

    builder.use(Logger) builder.use(Cache) builder.run(app) Rack::Handler::WEBrick.run(builder.to_app)
  12. class Middleware def initialize(next_app) @next_app = next_app end def call(env)

    # do something with env @next_app.call(env) end end
  13. def initialize(default_app = nil, &block) @use, @map, @run, @warmup, @freeze_app

    = [], nil, default_app, nil, false instance_eval(&block) if block_given? end def use(middleware, *args, &block) if @map mapping, @map = @map, nil @use << proc { |app| generate_map app, mapping } end @use << proc { |app| middleware.new(app, *args, &block) } end def run(app) @run = app end def call(env) to_app.call(env) end def to_app app = @map ? generate_map(@run, @map) : @run fail "missing run or map statement" unless app app.freeze if @freeze_app app = @use.reverse.inject(app) { |a,e| e[a].tap { |x| x.freeze if @freeze_app } } @warmup.call(app) if @warmup app end
  14. def initialize() @use = [] end def use(middleware) @use <<

    proc { |app| middleware.new(app) } end def run(app) @run = app end def to_app app = @run app = @use.reverse.inject(app) {|a,e| e[a]} app end
  15. app = ->(env){[‘200’, {'Content-Type' => 'text/html'}, ['<p>Hi</p>']]} builder = Rack::Builder.new

    builder.use(Logger) builder.use(Cache) builder.run(app) Rack::Handler::WEBrick.run(builder.to_app)
  16. rack_app = proc { |env| [200, {}, []]} [ proc

    { |app| Cache.new(app) }, proc { |app| Logger.new(app) }, ]
  17. class Logger def initialize(app) @app = app end def call(env)

    end end proc do |app| proc {|env| } end
  18. class Logger def initialize(app) @app = app end def call(env)

    @app.call(env) end end proc do |app| proc {|env| } end
  19. class Logger def initialize(app) @app = app end def call(env)

    @app.call(env) end end proc do |app| proc {|env| app.call(env)} end
  20. class Logger def initialize(app) @app = app end def call(env)

    @app.call(env) end end Logger = proc do |app| proc { |env| app.call(env) } end
  21. class Logger def initialize(app) @app = app end def call(env)

    @app.call(env) end end Logger = ->(app) do ->(env){ app.call(env) } end
  22. Logger = ->(app) do ->(env) do app.call(env) end end Cache

    = ->(app) do ->(env) do app.call(env) end end
  23. app = ->(req) do [200, {}, ["Hello"]] end Logger =

    ->(app) do ->(env) do app.call(env) end end Cache = ->(app) do ->(env) do app.call(env) end end
  24. app = ->(req) do [200, {}, ["Hello"]] end Logger =

    ->(app) do ->(env) do app.call(env) end end Cache = ->(app) do ->(env) do app.call(env) end end builder = Cache.call(app)
  25. app = ->(req) do [200, {}, ["Hello"]] end Logger =

    ->(app) do ->(env) do app.call(env) end end Cache = ->(app) do ->(env) do app.call(env) end end builder = Logger.call(Cache.call(app))
  26. app = ->(req) do [200, {}, ["Hello"]] end Logger =

    ->(app) do ->(env) do app.call(env) end end Cache = ->(app) do ->(env) do app.call(env) end end builder = Logger.call(Cache.call(app)) Rack::Handler::WEBrick.run(builder)
  27. app = ->(req) { [200, {}, ["Hello"]] } Logger =

    ->(app) { ->(env) { app.call(env) } } Cache = ->(app) { ->(env) { app.call(env) } } builder = Logger.call(Cache.call(app)) Rack::Handler::WEBrick.run(builder)
  28. app = ->(req) { [200, {}, ["Hello"]] } Logger =

    ->(app) { ->(env) { app.(env) } } Cache = ->(app) { ->(env) { app.(env) } } builder = Logger.call(Cache.call(app)) Rack::Handler::WEBrick.run(builder)
  29. (app = ->(req) { [200, {}, ["Hello"]] }) (Logger =

    ->(app) { ->(env) { (app.(env)) } }) (Cache = ->(app) { ->(env) { (app.(env)) } }) (builder = Logger.call(Cache.call(app))) Rack::Handler::WEBrick.run(builder)
  30. (app = ->(req) { [200, {}, ["Hello"]]}) (Logger = ->(app)

    { (->(env) { (app.(env))})}) (Cache = ->(app) { (->(env) { (app.(env))})}) (Rack::Handler::WEBrick.run(Logger.(Cache.(app))))
  31. (defn app [req] {:status 200 :headers {} :body ""}) (defn

    Logger [app] (fn [env] (app env))) (defn Cache [app] (fn [env] (app env))) (jetty/run-jetty (Logger(Cache app)) {:port 3000})
  32. (Logger = ->(app) { (->(env) { (app.(env))})}) (Cache = ->(app)

    { (->(env) { (app.(env))})}) (defn Logger [app] (fn [env] (app env))) (defn Cache [app] (fn [env] (app env)))
  33. def call(env) env["verdict_message"] = verdict_message(env["verdict"], env["name"]) @app.call(env) end private def

    verdict_message(verdict, name) if verdict == "guilty" "#{name} is guilty" else "#{name} is not guilty" end end
  34. def call(env) env["verdict_message"] = verdict_message(env["verdict"], env["name"]) @app.call(env) end private def

    verdict_message(verdict, name) first_name = name.split(" ").first if verdict == "guilty" "#{first_name} is guilty" else "#{first_name} is not guilty" en end
  35. def call(env) env["verdict_message"] = verdict_message(env["verdict"], env["name"]) @app.call(env) end private def

    verdict_message(verdict, name) first_name = name.split(" ").first if first_name == "Mr." || first_name == “Mrs." first_name = name.split(" ")[1] if verdict == "guilty" "#{first_name} is guilty" else "#{first_name} is not guilty" end else if verdict == "guilty" "#{first_name} is guilty" else "#{first_name} is not guilty" end end end
  36. def call(env) @name = env["name"] env["verdict_message"] = verdict_message(env["verdict"], env["name"]) @app.call(env)

    end private def verdict_message(verdict, name) first_name = name.split(" ").first if first_name == "Mr." || first_name == “Mrs." first_name = name.split(" ")[1] if verdict == "guilty" "#{first_name} is guilty" else "#{first_name} is not guilty" end else if verdict == "guilty" "#{first_name} is guilty" else "#{first_name} is not guilty" end end end
  37. def call(env) @name = env["name"] env["verdict_message"] = verdict_message(env["verdict"]) @app.call(env) end

    private def verdict_message(verdict) if verdict == "guilty" "#{name} is guilty" else "#{name} is not guilty" end end def name words = @name.split(" ") if words.first == "Mr." || "Mrs." || "Ms" words[1] else words[0] end end
  38. def freeze_app @freeze_app = true end def to_app app =

    @run app.freeze if @freeze_app app = @use.reverse.inject(app) { |a,e| e[a].tap { |x| x.freeze if @freeze_app } } app end
  39. def freeze_app @freeze_app = true end def to_app app =

    @run app.freeze if @freeze_app app = @use.reverse.inject(app) { |a,e| e[a].tap { |x| x.freeze if @freeze_app } } app end
  40. def freeze_app @freeze_app = true end def to_app app =

    @run app.freeze if @freeze_app app = @use.reverse.inject(app) { |a,e| e[a].tap { |x| x.freeze if @freeze_app } } app end
  41. class Person attr_reader :name def initialize(name) @name = name end

    end class Company attr_reader :name def initialize(name) @name = name end end
  42. class Person def update_name(name) @name = name end end class

    Company def change_name(name) @name = name end end
  43. class Person def update_name(name) @name = name end end class

    Company def change_name(name) @name = name end end
  44. class Person def update_name(name) @name = name end end class

    Company def change_name(name) @name = name end end
  45. (def person { :name "Alex" }) (def company { :name

    "VTS" }) (assoc person :name “Al")
  46. (def person { :name "Alex" }) (def company { :name

    "VTS" }) (assoc person :name “Al") => { :name “A1” }
  47. (def person { :name "Alex" }) (def company { :name

    "VTS" }) (assoc person :name “Al") => { :name “A1” } (assoc company :name “V”) => { :name “V” }
  48. def call(env) request = Rack::Request.new(env) request.params["ssn"] = censor(request.params["ssn"]) @app.call(env) end

    private def censor(ssn) ssn .delete("-") .chars .slice(5..9) .unshift("xxx-xx-") .join end
  49. def call(env) request = Rack::Request.new(env) request.params["ssn"] = censor(request.params["ssn"]) @app.call(env) end

    def call(env) request = Rack::Request.new(env) request.update_param("ssn", censor(request.params["ssn"])) @app.call(env) end
  50. 0

  51. 0

  52. no