$30 off During Our Annual Pro Sale. View Details »

The Lifecycle of a Rails Response

The Lifecycle of a Rails Response

This breathtaking documentary series combines rare action, unimaginable scale, impossible locations and intimate moments captured from the deepest depths of Rails internals. Last year, we followed the lifecycle of best loved, wildest and most elusive Rails components through a request, from the browser to a controller action. This year, our journey takes us across the great text/plain, following the flow of HTTP streams, taking in the spectacular Action View as we find our way back to the browser. Join us to unearth the amazing lifecycle of a Rails response.

Krystan HuffMenne

May 04, 2020
Tweet

More Decks by Krystan HuffMenne

Other Decks in Programming

Transcript

  1. INSIDE RAILS
    The Lifecycle of a Response

    View Slide

  2. View Slide

  3. View Slide

  4. https://skylight.io/r/railsconf
    Don’t struggle to learn why your app is slow.
    Get answers with Skylight.
    DO THE
    ANSWER DANCE.
    SKYLIGHT

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. app/controllers/hello_controller.rb
    Terminal
    $
    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80
    Picture: Masai Mara Wildlife / Matt Scobel / CC BY 3.0

    View Slide

  9. app/controllers/hello_controller.rb
    class SafariController < ActionController::Base
    def hello
    # ...do stuff here...
    render plain: 'Hello World'
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    Terminal
    $
    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80
    GET /safari HTTP/1.1
    > Host: skylight.io
    >
    Picture: Masai Mara Wildlife / Matt Scobel / CC BY 3.0

    View Slide

  10. app/controllers/hello_controller.rb
    Terminal
    $
    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80
    GET /safari HTTP/1.1
    > Host: skylight.io
    >
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 11
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Hello World
    app/controllers/hello_controller.rb
    class SafariController < ActionController::Base
    def hello
    # ...do stuff here...
    render plain: 'Hello World'
    end
    end
    app/controllers/safari_controller.rb
    Picture: Three Elephants in Early Morning / Yu Miyawaki / CC BY 3.0

    View Slide

  11. HELLO WORLD!
    http://skylight.io/safari

    View Slide

  12. app/controllers/hello_controller.rb
    http://skylight.io/safari
    Roar Savanna

    View Slide

  13. Ok…..?
    Hey, I have a request for you to handle.

    View Slide

  14. Sounds good.
    It’s a GET request for /safari, Host header is skylight.io
    200 OK. Content type is text/plain, body is “Roar Savanna”.
    Hey, I have a request for you to handle.

    View Slide

  15. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    Terminal
    $
    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80

    View Slide

  16. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    Terminal
    $
    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80
    GET /safari HTTP/1.1
    > Host: skylight.io
    >

    View Slide

  17. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    Terminal
    $
    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna
    GET /safari HTTP/1.1
    > Host: skylight.io
    >
    Roar Savanna

    View Slide

  18. handwave handwave
    Check out last year's talk for more info!

    View Slide

  19. RACK

    View Slide

  20. View Slide

  21. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    server.rb

    View Slide

  22. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    app.call(env)
    server.rb

    View Slide

  23. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    app.call(env)
    server.rb
    handwave handwave
    Check out last year's talk for more info!

    View Slide

  24. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    app.call(env)
    server.rb
    app/controllers/hello_controller.rb
    class SafariController < ActionController::Base
    def hello
    # Get it? A savanna is a type of plain...
    render plain: 'Roar Savanna'
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  25. app/controllers/hello_controller.rb
    http://skylight.io/safari
    app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    status, headers, body = app.call(env)
    status # => 200
    headers # => { 'Content-Type' => 'text/plain' }
    body # => [‘Roar Savanna’]
    server.rb
    1
    2
    3
    The Response Array

    View Slide

  26. app/controllers/hello_controller.rb
    http://skylight.io/safari
    Roar Savanna
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna

    View Slide

  27. app/controllers/hello_controller.rb
    http://skylight.io/safari
    Roar Savanna
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna

    View Slide

  28. View Slide

  29. app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    status, headers, body = app.call(env)
    status # => 200
    headers # => { 'Content-Type' => 'text/plain' }
    body # => [‘Roar Savanna’]
    server.rb
    1
    The Response Array

    View Slide

  30. 1xx —
    2xx —
    3xx —
    4xx —
    5xx —
    STATUS CODES
    Informational
    Success!!
    Redirection
    Client Error
    Server Error

    View Slide

  31. Here’s a GET request for /safari, Host header is skylight.io
    OMG I DID IT! Roar Savanna!
    Huh?

    View Slide

  32. Great, I can render that.
    Here’s a GET request for /safari, Host header is skylight.io
    200 OK; Roar Savanna

    View Slide

  33. Should I index the page at /safari ?
    500 Internal Server Error
    OK, I’ll be back later!

    How about now?
    200 OK; Roar Savanna
    Great! I’ve indexed it.

    View Slide

  34. 1xx —
    2xx —
    3xx —
    4xx —
    5xx —
    STATUS CODES
    Informational
    Success!!
    Redirection
    Client Error
    Server Error
    PRO-TIP!
    Learn a lot more about
    status codes by going to
    httpstatuses.com

    View Slide

  35. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    def eat_hippo
    consume_hippo if @current_user.lion?
    head :no_content # 204
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  36. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    def eat_hippo
    consume_hippo if @current_user.lion?
    head :no_content # 204
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  37. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    def eat_hippo
    consume_hippo if @current_user.lion?
    head :no_content # 204
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  38. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    def eat_hippo
    consume_hippo if @current_user.lion?
    head :no_content # 204
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  39. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    def eat_hippo
    consume_hippo if @current_user.lion?
    head :no_content # 204
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  40. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    before_action { redirect_to oasis_url if dry_season? }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  41. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    before_action { redirect_to oasis_url if dry_season? }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 302 Found
    < Location: https://www.skylight.io/oasis
    < Content-Type: text/plain
    < Date: Thu, 25 Apr 2019 18:52:54 GMT

    View Slide

  42. View Slide

  43. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    redirect_to oasis_url, status: :moved_permanently
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 301 Moved Permanently
    < Location: https://www.skylight.io/oasis
    < Content-Type: text/plain
    < Date: Thu, 25 Apr 2019 18:52:54 GMT

    View Slide

  44. app/controllers/hello_controller.rb
    Rails.application.routes.draw do
    get '/find-hippo', to: redirect('/oasis')
    end
    config/routes.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 301 Moved Permanently
    < Location: https://www.skylight.io/oasis
    < Content-Type: text/plain
    < Date: Thu, 25 Apr 2019 18:52:54 GMT

    View Slide

  45. DANGER!

    View Slide

  46. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    redirect_to oasis_url if dry_season?
    render :hippo
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  47. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    redirect_to oasis_url if dry_season?
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    http://skylight.io/find-hippo

    View Slide

  48. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    before_action { redirect_to oasis_url if dry_season? }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  49. Here’s a GET request for /safari, Host header is skylight.io
    200 OK; Roar Savanna
    Great, I can render that.
    Wait…I need more information.

    View Slide

  50. Here’s a GET request for /safari, Host header is skylight.io
    200 OK; Roar Savanna
    Great, I can render that.
    Wait…I need more information.
    Oh yeah. It’s a plain text response.
    Oh! OK!

    View Slide

  51. app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    status, headers, body = app.call(env)
    status # => 200
    headers # => { 'Content-Type' => 'text/plain' }
    body # => [‘Roar Savanna’]
    server.rb
    1
    2 The Response Array

    View Slide

  52. app/controllers/hello_controller.rb
    < HTTP/1.1 302 Found
    < Content-Type: text/plain
    < Location: https://www.skylight.io/oasis
    < Date: Thu, 25 Apr 2019 18:52:54 GMT

    View Slide

  53. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Set-Cookie: _safari_session=some-token;
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna

    View Slide

  54. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Set-Cookie: _safari_session=some-token;
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna

    View Slide

  55. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Set-Cookie: _safari_session=some-token;
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna

    View Slide

  56. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 12
    < Set-Cookie: _safari_session=some-token;
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Roar Savanna

    View Slide

  57. app/controllers/hello_controller.rb
    response.headers['HEADER NAME'] = 'header value'

    View Slide

  58. Here’s a GET request for /safari, Host header is skylight.io

    200 OK; Roar Savanna
    Thanks!

    View Slide

  59. Here’s a GET request for /safari, Host header is skylight.io
    200 OK; Roar Savanna
    Here’s a GET request for /safari, Host header is skylight.io
    Just use the last 200
    Nice!
    Thanks!

    View Slide

  60. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    PRO-TIP!
    Turn on caching in
    development by running
    rails dev:cache

    View Slide

  61. HIPPOS ARE BIG

    View Slide

  62. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    http_cache_forever { render :hippo }
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=3155695200, private
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  63. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    http_cache_forever { render :hippo }
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=3155695200, private
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  64. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    http_cache_forever(public: true) { render :hippo }
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=3155695200, public
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  65. app/controllers/hello_controller.rb
    <%= image_tag "hippo.png" %>
    app/views/safari/hippo.html.erb

    View Slide

  66. app/controllers/hello_controller.rb
    <%= image_tag "hippo.png" %>
    app/views/safari/hippo.html.erb
    app/controllers/hello_controller.rb
    http://skylight.io/find-hippo
    ca1895ae8ed0a09237f6992015fef1e11be77c023.png">

    View Slide

  67. app/controllers/hello_controller.rb
    <%= image_tag "hippo.png" %>
    app/views/safari/hippo.html.erb
    app/controllers/hello_controller.rb
    http://skylight.io/find-hippo
    ca1895ae8ed0a09237f6992015fef1e11be77c023.png">

    View Slide

  68. app/controllers/hello_controller.rb
    <%= image_tag "hippo.png" %>
    app/views/safari/hippo.html.erb
    app/controllers/hello_controller.rb
    http://skylight.io/find-hippo
    ca1895ae8ed0a09237f6992015fef1e11be77c023.png">

    View Slide

  69. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    http_cache_forever { render :hippo }
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=3155695200, private
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  70. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    expires_in 1.hour
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=3600, private
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  71. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    expires_in 1.hour
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=3600, private
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  72. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  73. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  74. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  75. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb



    Safari
    href="/assets/application-gobbledygook.css" />



    View Slide

  76. app/controllers/hello_controller.rb
    # a simplified Rack::ETag
    module Rack
    class ETag
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200
    digest = digest_body(body)
    headers[Etag] = %(W/"#{digest}")
    end
    [status, headers, body]
    end
    private
    #...
    end
    end
    rack/lib/rack/etag.rb

    View Slide

  77. app/controllers/hello_controller.rb
    # a simplified Rack::ETag
    module Rack
    class ETag
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200
    digest = digest_body(body)
    headers[Etag] = %(W/"#{digest}")
    end
    [status, headers, body]
    end
    private
    #...
    end
    end
    rack/lib/rack/etag.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“48a7e47309e0ec54e32df3a272094025"
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  78. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“48a7e47309e0ec54e32df3a272094025"
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …
    app/controllers/hello_controller.rb
    > GET /find-hippo HTTP/1.1
    > Host: skylight.io
    > If-None-Match: W/"48a7e47309e0ec54e32df3a272094025"

    View Slide

  79. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb



    Safari
    href="/assets/application-gobbledygook.css" />



    View Slide

  80. app/controllers/hello_controller.rb
    # a simplified Rack::ETag
    module Rack
    class ETag
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200
    digest = digest_body(body)
    headers[Etag] = %(W/"#{digest}")
    end
    [status, headers, body]
    end
    private
    #...
    end
    end
    rack/lib/rack/etag.rb

    View Slide

  81. app/controllers/hello_controller.rb
    # a simplified Rack::ETag
    module Rack
    class ETag
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200
    digest = digest_body(body)
    headers[Etag] = %(W/"#{digest}")
    end
    [status, headers, body]
    end
    private
    #...
    end
    end
    rack/lib/rack/etag.rb

    View Slide

  82. app/controllers/hello_controller.rb
    # a simplified Rack::ETag
    module Rack
    class ETag
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200
    digest = digest_body(body)
    headers[Etag] = %(W/"#{digest}")
    end
    [status, headers, body]
    end
    private
    #...
    end
    end
    rack/lib/rack/etag.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“48a7e47309e0ec54e32df3a272094025"
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  83. app/controllers/hello_controller.rb
    # a simplified Rack::ConditionalGet
    module Rack
    class ConditionalGet
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200 && etag_matches?
    status = 304
    body = []
    end
    [status, headers, body]
    end
    private
    def etag_matches?
    headers['ETag'] == env['HTTP_IF_NONE_MATCH']
    end
    end
    end
    rack/lib/rack/conditional_get.rb

    View Slide

  84. app/controllers/hello_controller.rb
    # a simplified Rack::ConditionalGet
    module Rack
    class ConditionalGet
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200 && etag_matches?
    status = 304
    body = []
    end
    [status, headers, body]
    end
    private
    def etag_matches?
    headers['ETag'] == env['HTTP_IF_NONE_MATCH']
    end
    end
    end
    rack/lib/rack/conditional_get.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 304 Not Modified
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“48a7e47309e0ec54e32df3a272094025”
    < Content-Type: text/html
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <

    View Slide

  85. app/controllers/hello_controller.rb
    # a simplified Rack::ConditionalGet
    module Rack
    class ConditionalGet
    def initialize(app)
    @app = app
    end
    def call(env)
    status, headers, body = @app.call(env)
    if status == 200 && etag_matches?
    status = 304
    body = []
    end
    [status, headers, body]
    end
    private
    def etag_matches?
    headers['ETag'] == env['HTTP_IF_NONE_MATCH']
    end
    end
    end
    rack/lib/rack/conditional_get.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“48a7e47309e0ec54e32df3a272094025”
    < Content-Type: text/html
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  86. View Slide

  87. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo if stale?(@hippo)
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“48a7e47309e0ec54e32df3a272094025"
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/"60bee75b8031cda7761a332685151766"
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  88. app/controllers/hello_controller.rb



    Safari
    href="/assets/application-gobbledygook.css" />





    View Slide

  89. app/controllers/hello_controller.rb
    "hippo/1-20071224150000"

    View Slide

  90. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo if stale?(@hippo)
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/"60bee75b8031cda7761a332685151766"
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  91. View Slide

  92. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    response.headers["Cache-Control"] = "no-store"
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: no-store
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  93. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    response.headers["Cache-Control"] = "no-store"
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Cache-Control: no-store
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …
    no-store ≠ no-cache

    View Slide

  94. THE RESPONSE BODY
    and finally…

    View Slide

  95. app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => ‘/safari',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    status, headers, body = app.call(env)
    status # => 200
    headers # => { 'Content-Type' => 'text/plain' }
    body # => [‘Roar Savanna’]
    server.rb
    1
    2
    3
    The Response Array

    View Slide

  96. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb



    Safari
    href="/assets/application-gobbledygook.css" />



    View Slide

  97. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  98. http://skylight.io/safari.html

    View Slide

  99. app/controllers/hello_controller.rb
    > GET /find-hippo HTTP/1.1
    > Host: skylight.io
    > Accept: text/html,application/xhtml+xml,application/xml;

    View Slide

  100. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  101. http://skylight.io/find-hippo.json

    View Slide

  102. http://skylight.io/find-hippo.json

    View Slide

  103. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    respond_to do |format|
    format.html { render :hippo }
    format.json { render json: @hippo }
    end
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  104. http://skylight.io/find-hippo
    {
    "id": 1,
    "name": "Jason",
    "created_at": "2020-04-10T17:14:20.771Z",
    "updated_at": "2020-04-10T17:14:20.771Z"
    }
    app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Cache-Control: max-age=0, private, must-revalidate
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    < Content-Type: application/json
    <
    < {"id": 1, "name": “Jason", … }

    View Slide

  105. app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < X-Content-Type-Options: nosniff
    < Cache-Control: max-age=0, private, must-revalidate
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < …

    View Slide

  106. TEMPLATE RENDERING

    View Slide

  107. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb

    View Slide

  108. app/controllers/hello_controller.rb
    class SafariController < ApplicationController
    before_action { @hippo = Hippo.first }
    def find_hippo
    render :hippo
    end
    end
    app/controllers/safari_controller.rb
    app/controllers/hello_controller.rb
    Hey <%= current_user.name %>, meet <%= link_to @hippo.name, @hippo %>!
    app/views/safari/hippo.html.erb

    View Slide

  109. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end
    NOTE: To see the actual code, look in ActionView!::Rendering, ActionView!::Base,
    ActionView!::Renderer, ActionView!::TemplateRenderer, ActionView!::Template

    View Slide

  110. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  111. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  112. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  113. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  114. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  115. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  116. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  117. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  118. app/controllers/hello_controller.rb
    class SafariControllerViewContext < ActionView::Base
    include Rails::AllTheHelpers
    # link_to, etc.
    include MyApp::AllTheHelpers
    # current_user, etc.
    def initialize(assigns)
    assigns.each { |k, v| instance_variable_set("@#{k}", v) }
    end
    private
    # Hey <%= current_user.name %>, meet
    # <%= link_to @hippo.name, @hippo %>!
    def __compiled_app_templates_hippo_erb
    output = ""
    output << "Hey "
    output << html_escape(current_user.name)
    output << ", meet"
    output << link_to(html_escape(@hippo.name), @hippo)
    output << "!"
    output
    end
    end

    View Slide

  119. http://skylight.io/find-hippo



    Safari




    Hey RailsConf, meet Phyllis!


    Hey RailsConf, meet Phyllis!

    View Slide

  120. http://skylight.io/find-hippo



    Safari




    Hey RailsConf, meet Phyllis!


    Hey RailsConf, meet Phyllis! app/controllers/hello_controller.rb
    < HTTP/1.1 200 OK
    < Content-Type: text/html
    < Content-Length: 306
    < Cache-Control: max-age=0, private, must-revalidate
    < ETag: W/“60bee75b8031cda7761a332685151766”
    < Set-Cookie: _safari_session=some-session-token; path=/;
    < X-Content-Type-Options: nosniff
    < Date: Thu, 25 Apr 2019 18:52:54 GMT

    View Slide

  121. View Slide