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

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 full-size slide

  2. 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 full-size slide

  3. 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 full-size slide

  4. 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 full-size slide

  5. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  9. 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 full-size slide

  10. 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 full-size slide

  11. 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 full-size slide

  12. 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 full-size slide

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

    View full-size slide

  14. 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 full-size slide

  15. 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 full-size slide

  16. 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 full-size slide

  17. 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 full-size slide

  18. 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 full-size slide

  19. 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 full-size slide

  20. 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 full-size slide

  21. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. 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 full-size slide

  29. 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 full-size slide

  30. 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 full-size slide

  31. 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 full-size slide

  32. 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 full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. 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 full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. 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 full-size slide

  41. 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 full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 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 full-size slide

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

    View full-size slide

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

    200 OK; Roar Savanna
    Thanks!

    View full-size slide

  49. 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 full-size slide

  50. 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 full-size slide

  51. HIPPOS ARE BIG

    View full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

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

    View full-size slide

  56. 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 full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

  61. 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 full-size slide

  62. 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 full-size slide

  63. 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 full-size slide

  64. 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 full-size slide

  65. 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 full-size slide

  66. 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 full-size slide

  67. 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 full-size slide

  68. 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 full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

  72. 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 full-size slide

  73. 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 full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

  76. 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 full-size slide

  77. app/controllers/hello_controller.rb



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





    View full-size slide

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

    View full-size slide

  79. 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 full-size slide

  80. 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 full-size slide

  81. 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 full-size slide

  82. THE RESPONSE BODY
    and finally…

    View full-size slide

  83. 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 full-size slide

  84. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  88. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  91. 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 full-size slide

  92. 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 full-size slide

  93. 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 full-size slide

  94. TEMPLATE RENDERING

    View full-size slide

  95. 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 full-size 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
    Hey <%= current_user.name %>, meet <%= link_to @hippo.name, @hippo %>!
    app/views/safari/hippo.html.erb

    View full-size slide

  97. 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 full-size slide

  98. 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 full-size slide

  99. 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 full-size slide

  100. 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 full-size slide

  101. 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 full-size slide

  102. 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 full-size slide

  103. 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 full-size slide

  104. 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 full-size slide

  105. 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 full-size slide

  106. 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 full-size slide

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



    Safari




    Hey RailsConf, meet Phyllis!


    Hey RailsConf, meet Phyllis!

    View full-size slide

  108. 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 full-size slide