Slide 1

Slide 1 text

Rack 'em Stack 'em Web Apps @jasonrclark art by asher clark

Slide 2

Slide 2 text

http://fight.robotlikes.com 2

Slide 3

Slide 3 text

What Is Rack? 3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

5 x browser server rack ----- ------ --- ---- rails

Slide 6

Slide 6 text

Why Rack? 6

Slide 7

Slide 7 text

7 Speed Simplicity Reuse

Slide 8

Slide 8 text

Show Me The Code!? 8

Slide 9

Slide 9 text

9 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 10

Slide 10 text

10 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 11

Slide 11 text

11 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 12

Slide 12 text

12 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 13

Slide 13 text

13 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 14

Slide 14 text

14 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 15

Slide 15 text

15 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 16

Slide 16 text

16 $ terminal $ rackup [2017-04-17] INFO WEBrick 1.3.1 [2017-04-17] INFO ruby 2.4.1 [2017-04-17] INFO WEBrick::HTTPServer#start: pid=32555 port=9292

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

18 $ terminal $ curl http://localhost:9292 Good enough

Slide 19

Slide 19 text

Rack::Test 19

Slide 20

Slide 20 text

20 # Gemfile gem "rack" gem "rack-test"

Slide 21

Slide 21 text

21 # test/test_helper.rb require "minitest/autorun" require "rack/test"

Slide 22

Slide 22 text

22 # app_test.rb class ApplicationTest < Minitest::Test include Rack::Test::Methods def app Application.new end # ... end

Slide 23

Slide 23 text

23 # app_test.rb class ApplicationTest < Minitest::Test include Rack::Test::Methods def app Application.new end # ... end

Slide 24

Slide 24 text

24 # app_test.rb class ApplicationTest < Minitest::Test include Rack::Test::Methods def app Application.new end # ... end

Slide 25

Slide 25 text

25 # app_test.rb class ApplicationTest < Minitest::Test def test_its_good_enough get "/" assert last_response.ok? assert_equal "Good enough", last_response.body end end

Slide 26

Slide 26 text

26 # app_test.rb class ApplicationTest < Minitest::Test def test_its_good_enough get "/" assert last_response.ok? assert_equal "Good enough", last_response.body end end

Slide 27

Slide 27 text

27 # app_test.rb class ApplicationTest < Minitest::Test def test_its_good_enough get "/" assert last_response.ok? assert_equal "Good enough", last_response.body end end

Slide 28

Slide 28 text

Think About The Environment! 28

Slide 29

Slide 29 text

29 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 30

Slide 30 text

30 # config.ru class Application def call(env) [200, {}, ["Good enough"]] end end run Application.new

Slide 31

Slide 31 text

31 # env { "REMOTE_ADDR"=>"::1", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://localhost/bot", "PATH_INFO"=>"/bot", "QUERY_STRING"=>"", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Mac)", "rack.input"=> #, # ... }

Slide 32

Slide 32 text

32 # env { "REMOTE_ADDR"=>"::1", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://localhost/bot", "PATH_INFO"=>"/bot", "QUERY_STRING"=>"", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Mac)", "rack.input"=> #, # ... }

Slide 33

Slide 33 text

33 # env { "REMOTE_ADDR"=>"::1", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://localhost/bot", "PATH_INFO"=>"/bot", "QUERY_STRING"=>"", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Mac)", "rack.input"=> #, # ... }

Slide 34

Slide 34 text

34 # env { "REMOTE_ADDR"=>"::1", "REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://localhost/bot", "PATH_INFO"=>"/bot", "QUERY_STRING"=>"", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Mac)", "rack.input"=> #, # ... }

Slide 35

Slide 35 text

35 # config.ru class Application def call(env) if env["PATH_INFO"] == "/bot" [403, {}, ["Beep beep intruder"]] else [200, {}, ["Good enough"]] end end end

Slide 36

Slide 36 text

36 # config.ru class Application def call(env) if env["PATH_INFO"] == "/bot" [403, {}, ["Beep beep intruder"]] else [200, {}, ["Good enough"]] end end end

Slide 37

Slide 37 text

Routing 37

Slide 38

Slide 38 text

38 Frameworks help!

Slide 39

Slide 39 text

39 # config.ru class Status def call(env) # ... end end map "/status" do run Status.new end map "/" do run Application.new end

Slide 40

Slide 40 text

40 # config.ru class Status def call(env) # ... end end map "/status" do run Status.new end map "/" do run Application.new end

Slide 41

Slide 41 text

41 # config.ru class Status def call(env) # ... end end run Rack::URLMap.new( "/status" => Status.new, "/" => Application.new )

Slide 42

Slide 42 text

Rack::Request 42

Slide 43

Slide 43 text

43 # app.rb class Application def call(env) request = Rack::Request.new(env) if request.path_info == "/bot" [403, {}, ["Beep intruder"]] else [200, {}, ["Good enough"]] end end end

Slide 44

Slide 44 text

44 # app.rb class Application def call(env) request = Rack::Request.new(env) if request.path_info.match %r{/bot/(\d+)} [403, {}, ["Beep intruder on #{$1}"]] else [200, {}, ["Good enough"]] end end end

Slide 45

Slide 45 text

45 Isn't there an easier way!?

Slide 46

Slide 46 text

46 Frameworks help!

Slide 47

Slide 47 text

47 # app.rb class Application def call(env) request = Rack::Request.new(env) if request.path_info.match %r{/bot/(\d+)} bot = Database.find($1) [200, {}, [bot.to_s]] else # ... end end end

Slide 48

Slide 48 text

48 $ terminal $ curl http://localhost:9292/bot/1 <--[1]-->

Slide 49

Slide 49 text

GET 49

Slide 50

Slide 50 text

POST 50

Slide 51

Slide 51 text

51 # app.rb if request.path_info.match %r{/bot/(\d+)} if request.get? bot = Database.find($1) [200, {}, [bot.to_s]] elsif request.post? bot = request.body.read Database.save($1, bot) [200, {}, ["Wrote bot #{$1}"]] end end

Slide 52

Slide 52 text

52 # app.rb if request.path_info.match %r{/bot/(\d+)} if request.get? bot = Database.find($1) [200, {}, [bot.to_s]] elsif request.post? bot = request.body.read Database.save($1, bot) [200, {}, ["Wrote bot #{$1}"]] end end

Slide 53

Slide 53 text

53 # app.rb if request.path_info.match %r{/bot/(\d+)} if request.get? bot = Database.find($1) [200, {}, [bot.to_s]] elsif request.post? bot = request.body.read Database.save($1, bot) [200, {}, ["Wrote bot #{$1}"]] end end

Slide 54

Slide 54 text

54 # app.rb if request.path_info.match %r{/bot/(\d+)} if request.get? bot = Database.find($1) [200, {}, [bot.to_s]] elsif request.post? bot = request.body.read Database.save($1, bot) [200, {}, ["Wrote bot #{$1}"]] end end

Slide 55

Slide 55 text

55 # app.rb if request.path_info.match %r{/bot/(\d+)} if request.get? bot = Database.find($1) [200, {}, [bot.to_s]] elsif request.post? bot = request.body.read Database.save($1, bot) [200, {}, ["Wrote bot #{$1}"]] end end

Slide 56

Slide 56 text

56 # app.rb if request.path_info.match %r{/bot/(\d+)} if request.get? bot = Database.find($1) [200, {}, [bot.to_s]] elsif request.post? bot = request.body.read Database.save($1, bot) [200, {}, ["Wrote bot #{$1}"]] end end

Slide 57

Slide 57 text

57 $ terminal $ curl -X POST http://localhost:9292/bot/2 --data "\[*]/" Wrote bot 2

Slide 58

Slide 58 text

58 $ terminal $ curl -X POST http://localhost:9292/bot/2 --data "\[*]/" Wrote bot 2 $ curl http://localhost:9292/bot/2 \[*]/

Slide 59

Slide 59 text

Rack::Response 59

Slide 60

Slide 60 text

60 # app.rb def call(env) response = Rack::Response.new if #... bot = Database.find($1) response.write(bot.to_s) end response.finish end

Slide 61

Slide 61 text

61 # app.rb def call(env) response = Rack::Response.new if #... bot = Database.find($1) response.write(bot.to_s) end response.finish end

Slide 62

Slide 62 text

62 # app.rb def call(env) response = Rack::Response.new if #... bot = Database.find($1) response.write(bot.to_s) end response.finish end

Slide 63

Slide 63 text

63 # app.rb def call(env) response = Rack::Response.new if #... bot = Database.find($1) response.write(bot.to_s) end response.finish end

Slide 64

Slide 64 text

64 $ pry > response.finish [200, {"Content-Length"=>"11"}, #]

Slide 65

Slide 65 text

65 $ pry > response.finish [200, {"Content-Length"=>"11"}, #]

Slide 66

Slide 66 text

Middleware 66

Slide 67

Slide 67 text

67 # config.ru require 'app' use Middleware run Application.new

Slide 68

Slide 68 text

68 # config.ru require 'app' use Middleware run Application.new

Slide 69

Slide 69 text

69 middleware middleware ----- ------ --- ---- app ----- ------ --- ---- ----- ------ --- ---- call() call() call()

Slide 70

Slide 70 text

70 # middleware.rb class Middleware def initialize(app, _opts={}) @app = app end def call(env) @app.call(env) end end

Slide 71

Slide 71 text

71 # middleware.rb class Middleware def initialize(app, _opts={}) @app = app end def call(env) @app.call(env) end end

Slide 72

Slide 72 text

72 # middleware.rb class Middleware def initialize(app, _opts={}) @app = app end def call(env) @app.call(env) end end

Slide 73

Slide 73 text

73 # middleware.rb def call(env) req = Rack::Request.new(env) key = req.get_header("HTTP_X_API_KEY") if key == "beep" @app.call(env) else [403, {}, ["FORBIDDEN! BEEP"]] end end

Slide 74

Slide 74 text

74 # middleware.rb def call(env) req = Rack::Request.new(env) key = req.get_header("HTTP_X_API_KEY") if key == "beep" @app.call(env) else [403, {}, ["FORBIDDEN! BEEP"]] end end

Slide 75

Slide 75 text

75 # middleware.rb def call(env) req = Rack::Request.new(env) key = req.get_header("HTTP_X_API_KEY") if key == "beep" @app.call(env) else [403, {}, ["FORBIDDEN! BEEP"]] end end

Slide 76

Slide 76 text

76 # middleware.rb def call(env) req = Rack::Request.new(env) key = req.get_header("HTTP_X_API_KEY") if key == "beep" @app.call(env) else [403, {}, ["FORBIDDEN! BEEP"]] end end

Slide 77

Slide 77 text

77 middleware ----- ------ --- ---- app ----- ------ --- ---- call() nope!

Slide 78

Slide 78 text

78 $ terminal $ curl http://localhost:9292/bot/1 FORBIDDEN! BEEP

Slide 79

Slide 79 text

79 $ terminal $ curl http://localhost:9292/bot/1 FORBIDDEN! BEEP $ curl -H "X-Api-Key: beep" http://localhost:9292/bot/1 <--[1]-->

Slide 80

Slide 80 text

80 Rack::Static Rack::Session::* Rack::ShowExceptions Rack::Reloader

Slide 81

Slide 81 text

Frameworks 81

Slide 82

Slide 82 text

82 middleware middleware ----- ------ --- ---- rails ----- ------ --- ---- call() call() call()

Slide 83

Slide 83 text

83 # config/routes.rb Rails.application.routes.draw do mount RackApplication.new, at: "/api" end

Slide 84

Slide 84 text

84 # config/application.rb class Application < Rails::Application config.middleware.use RackMiddleware config.middleware.insert_after Rack::Static, AnotherMiddleware end

Slide 85

Slide 85 text

85 # config/application.rb class Application < Rails::Application config.middleware.use RackMiddleware config.middleware.insert_after Rack::Static, AnotherMiddleware end

Slide 86

Slide 86 text

86 # config/application.rb class Application < Rails::Application config.middleware.use RackMiddleware config.middleware.insert_after Rack::Static, AnotherMiddleware end

Slide 87

Slide 87 text

87 middleware middleware ----- ------ --- ---- rails ----- ------ --- ---- call() call() call()

Slide 88

Slide 88 text

88 middleware middleware ---- ---- --- ---- rails ---- ---- --- ---- middleware -- --- -- ---

Slide 89

Slide 89 text

89 middleware middleware ----- ------ --- ---- sinatra ----- ------ --- ---- call() call() call()

Slide 90

Slide 90 text

90 # sinatra_app.rb class Application < Sinatra::Base get "/" do [200, {}, ["Wut!?"]] end end

Slide 91

Slide 91 text

91 # sinatra_app.rb class Application < Sinatra::Base get "/" do [200, {}, ["Wut!?"]] end end

Slide 92

Slide 92 text

https://bit.ly/rack-apps What, Why, and How Request and Response Middleware Frameworks 92 ??