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

Debugging Adventures in Rack-land (Fog City Rub...

Debugging Adventures in Rack-land (Fog City Ruby - November 2018)

I ran into a weird bug the other day. Editing an attribute in a web form would kick off an XHR request to the server and update a row in the database. But when the page was refreshed, the attribute on the page had not changed from its original value. Even weirder: refreshing the page a second time caused the attribute to show up correctly—and reflect what was in the database.

I’m not going to spoil the story here, but the issue led me into the internals of Rack middleware. Join me as we explore the details of this strange bug. Together, we’ll learn something about the wonderful world of Rack.

Avatar for Pan Thomakos

Pan Thomakos

November 13, 2018
Tweet

More Decks by Pan Thomakos

Other Decks in Programming

Transcript

  1. @panthomakos From: $HOME/web/app/controllers/settings_controller.rb @ line 313 SettingsController#athlete: 312: def athlete

    => 313: binding.pry 314: Permissions.decorate(current_athlete, self) 315: end 2.3.3 (...):0 >
  2. @panthomakos From: $HOME/web/app/controllers/settings_controller.rb @ line 313 SettingsController#athlete: 312: def athlete

    => 313: binding.pry 314: Permissions.decorate(current_athlete, self) 315: end 2.3.3 (...):0 > current_athlete.measurement_preference => :meters 2.3.3 (...):0 > Athlete.find(current_athlete.id).measurement_preference => :feet
  3. @panthomakos From: $HOME/web/app/controllers/settings_controller.rb @ line 313 SettingsController#athlete: 312: def athlete

    => 313: binding.pry 314: Permissions.decorate(current_athlete, self) 315: end 2.3.3 (...):0 > current_athlete.measurement_preference => :meters 2.3.3 (...):0 > Athlete.find(current_athlete.id).measurement_preference => :feet 2.3.3 (...):0 > Athlete.fetch(current_athlete.id).measurement_preference => :meters
  4. @panthomakos From: $HOME/web/app/controllers/settings_controller.rb @ line 313 SettingsController#athlete: 312: def athlete

    => 313: binding.pry 314: Permissions.decorate(current_athlete, self) 315: end 2.3.3 (...):0 > Athlete.fetch(current_athlete.id).measurement_preference => :meters 2.3.3 (...):0 > Strava::EntityMap.clear! => nil 2.3.3 (...):0 > Athlete.fetch(current_athlete.id).measurement_preference => :feet
  5. @panthomakos class Body def each yield “<html>” yield “<head> ...

    </head>” yield “<body> ... </body>” yield “</html>” end end
  6. @panthomakos class MyTimer def initialize(app) @app = app end def

    call(env) start = ::Timer.cputime response = @app.call(env) finish = ::Timer.cputime puts “Response Time: #{finish - start}ms” response end end
  7. @panthomakos class MyTimer def initialize(app) @app = app end def

    call(env) start = ::Timer.cputime response = @app.call(env) finish = ::Timer.cputime puts “Response Time: #{finish - start}ms” response end end
  8. @panthomakos # lib/rack/handler/webrick.rb def service(req, res) ... status, headers, body

    = @app.call(env) begin ... body.each { |part| res.body << part } ensure body.close if body.respond_to?(:close) end end
  9. @panthomakos class MyTimer def initialize(app) @app = app end def

    call(env) start = ::Timer.cputime response = @app.call(env) finish = ::Timer.cputime puts "Response Time: #{finish - start}ms" response end end
  10. @panthomakos class BoxyProxy def initialize(body, &close) @body = body @close

    = close end def each @body.each end def close @body.close if @body.respond_to?(:close) @close.call end end
  11. @panthomakos class MyTimer def call(env) start = ::Timer.cputime status, headers,

    body = @app.call(env) proxy = BodyProxy.new(body) do finish = ::Timer.cputime puts "Response Time: #{finish - start}ms" end [status, headers, proxy] end end
  12. @panthomakos Rack Handler Ruby Application Rack Middleware Rack Middleware Rack

    Middleware Req Res Body Body Body Body #close #close #close #close
  13. @panthomakos From: $HOME/web/app/controllers/settings_controller.rb @ line 313 SettingsController#athlete: 312: def athlete

    => 313: binding.pry 314: Permissions.decorate(current_athlete, self) 315: end 2.3.3 (...):0 > Athlete.fetch(current_athlete.id).measurement_preference => :meters 2.3.3 (...):0 > Strava::EntityMap.clear! => nil 2.3.3 (...):0 > Athlete.fetch(current_athlete.id).measurement_preference => :feet
  14. @panthomakos # activerecord/lib/active_record/identity_map.rb class Middleware def initialize(app) @app = app

    end def call(env) status, headers, body = @app.call(env) [status, headers, BodyProxy.new(body)] end end
  15. @panthomakos # activerecord/lib/active_record/identity_map.rb class Middleware class BodyProxy def initialize(target) @target

    = target end def each(&block) @target.each(&block) end def close @target.close if @target.respond_to?(:close) ensure IdentityMap.clear end end end
  16. @panthomakos # lib/strava/entity_map_middleware.rb module Strava class EntityMapMiddleware def initialize(app) @app

    = app end def call(env) status, headers, body = @app.call(env) [status, headers, BodyProxy.new(body)] end end end
  17. @panthomakos # lib/strava/entity_map_middleware.rb module Strava class EntityMapMiddleware class BodyProxy def

    initialize(target) @target = target end def each(&block) @target.each(&block) end def close @target.close if @target.respond_to?(:close) ensure ::Strava::EntityMap.clear! end end end end
  18. @panthomakos def close raise 'This is supposed to happen!' @target.close

    if @target.respond_to?(:close) ensure ::Strava::EntityMap.clear! end
  19. @panthomakos # lib/rack/handler/webrick.rb def service(req, res) ... status, headers, body

    = @app.call(env) begin ... body.each { |part| res.body << part } ensure body.close if body.respond_to?(:close) end end
  20. @panthomakos # lib/rack/handler/webrick.rb def service(req, res) ... status, headers, body

    = @app.call(env) begin ... body.each { |part| res.body << part } ensure binding.pry body.close if body.respond_to?(:close) end end
  21. @panthomakos From: .../lib/rack/handler/webrick.rb @ line 75 Rack::Handler::WEBrick#service: 70: } 71:

    body.each { |part| 72: res.body << part 73: } 74: ensure => 75: binding.pry 76: body.close if body.respond_to? :close 77: end 78: end 79: end 80: end 2.3.3 (...):0 >
  22. @panthomakos 2.3.3 (...):0 > body => #<Rack::BodyProxy:0x0055b6af04bad0 @block=#<Proc:0x0055b6af04baa8@.../rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6af04c318

    @block= #<Proc:0x0055b6af04c2c8@.../action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x00... @connection_id=47121566960060, @original_cache_value=false, @target=["{\"success\":true}"]>, @testing=false>, @closed=false>, @closed=false>
  23. @panthomakos 2.3.3 (...):0 > body => #<Rack::BodyProxy:0x0055b6af04bad0 @block=#<Proc:0x0055b6af04baa8@.../rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6af04c318

    @block= #<Proc:0x0055b6af04c2c8@.../action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x00... @connection_id=47121566960060, @original_cache_value=false, @target=["{\"success\":true}"]>, @testing=false>, @closed=false>, @closed=false>
  24. @panthomakos 2.3.3 (...):0 > body => #<Rack::BodyProxy:0x0055b6af04bad0 @block=#<Proc:0x0055b6af04baa8@.../rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6af04c318

    @block= #<Proc:0x0055b6af04c2c8@.../action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x00... @connection_id=47121566960060, @original_cache_value=false, @target=["{\"success\":true}"]>, @testing=false>, @closed=false>, @closed=false>
  25. @panthomakos From: .../lib/rack/handler/webrick.rb @ line 75 Rack::Handler::WEBrick#service: 70: } 71:

    body.each { |part| 72: res.body << part 73: } 74: ensure => 75: binding.pry 76: body.close if body.respond_to? :close 77: end 78: end 79: end 80: end 2.3.3 (...):0 >
  26. @panthomakos 2.3.3 (#<Rack::Handler::WEBrick:0x0055b6ae171168>):0 > body => #<Rack::BodyProxy:0x0055b6af109b70 @block=#<Proc:[email protected]/rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6af10a368

    @block= #<Proc:[email protected]/action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x00... @connection_id=47121546748840, @original_cache_value=false, @target= ["<!DOCTYPE html>\n<html class='logged-in clean s-minifeed old-login' dir='ltr' lang='en-US'..."]
  27. @panthomakos 2.3.3 (#<Rack::Handler::WEBrick:0x0055b6ae171168>):0 > body => #<Rack::BodyProxy:0x0055b6af109b70 @block=#<Proc:[email protected]/rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6af10a368

    @block= #<Proc:[email protected]/action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x00... @connection_id=47121546748840, @original_cache_value=false, @target= ["<!DOCTYPE html>\n<html class='logged-in clean s-minifeed old-login' dir='ltr' lang='en-US'..."]
  28. @panthomakos 2.3.3 (#<Rack::Handler::WEBrick:0x0055b6ae171168>):0 > body => #<Rack::BodyProxy:0x0055b6af109b70 @block=#<Proc:[email protected]/rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6af10a368

    @block= #<Proc:[email protected]/action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x00... @connection_id=47121546748840, @original_cache_value=false, @target= ["<!DOCTYPE html>\n<html class='logged-in clean s-minifeed old-login' dir='ltr' lang='en-US'..."]
  29. @panthomakos From: .../lib/rack/handler/webrick.rb @ line 75 Rack::Handler::WEBrick#service: 70: } 71:

    body.each { |part| 72: res.body << part 73: } 74: ensure => 75: binding.pry 76: body.close if body.respond_to? :close 77: end 78: end 79: end 80: end 2.3.3 (...):0 >
  30. @panthomakos 2.3.3 (#<Rack::Handler::WEBrick:0x0055b6ae6ca100>):0 > body => #<Rack::BodyProxy:0x0055b6ae31a1e0 @block=#<Proc:[email protected]/rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6ae31a898

    @block= #<Proc:[email protected]/action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x0055b6ae31a960 @connection_id=47121546748840, @original_cache_value=false, @target= #<Strava::EntityMapMiddleware::BodyProxy:0x00... @target=[]>>, @testing=false>, @closed=false>, @closed=false>
  31. @panthomakos 2.3.3 (#<Rack::Handler::WEBrick:0x0055b6ae6ca100>):0 > body => #<Rack::BodyProxy:0x0055b6ae31a1e0 @block=#<Proc:[email protected]/rack/lock.rb:16>, @body= #<ActionDispatch::BodyProxy:0x0055b6ae31a898

    @block= #<Proc:[email protected]/action_dispatch/middleware/reloader.rb:66>, @body= #<ActiveRecord::ConnectionAdapters::ConnectionManagement::Proxy:0x00... @body= #<ActiveRecord::QueryCache::BodyProxy:0x0055b6ae31a960 @connection_id=47121546748840, @original_cache_value=false, @target= #<Strava::EntityMapMiddleware::BodyProxy:0x00... @target=[]>>, @testing=false>, @closed=false>, @closed=false>
  32. @panthomakos 2.3.3 (...):0 > env['REQUEST_PATH'] => "/assets/application.css" 2.3.3 (...):0 >

    env['REQUEST_PATH'] => "/assets/strava/i18n/locales/en-US.js"
  33. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport use Strava::EntityMapMiddleware run Strava::Application.routes
  34. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport use Strava::EntityMapMiddleware run Strava::Application.routes
  35. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport use Strava::EntityMapMiddleware run Strava::Application.routes
  36. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport use Strava::EntityMapMiddleware run Strava::Application.routes
  37. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport use Strava::EntityMapMiddleware run Strava::Application.routes
  38. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport use Strava::EntityMapMiddleware run Strava::Application.routes
  39. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use Strava::EntityMapMiddleware use ActionDispatch::BestStandardsSupport run Strava::Application.routes
  40. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use Strava::EntityMapMiddleware use ActionDispatch::BestStandardsSupport run Strava::Application.routes
  41. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag use Strava::EntityMapMiddleware use ActionDispatch::BestStandardsSupport run Strava::Application.routes
  42. @panthomakos $ bundle exec rake middleware use Rack::Cors use Airbrake::UserInformer

    use ActionDispatch::Static use Rack::Lock use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use Airbrake::Rails::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Strava::EntityMapMiddleware use Rack::ETag use ActionDispatch::BestStandardsSupport run Strava::Application.routes
  43. @panthomakos def call(env) status, headers, body = @app.call(env) if etag_status?(status)

    && etag_body?(body) && !skip_caching?(headers) digest, body = digest_body(body) headers['ETag'] = %("#{digest}") if digest end unless headers['Cache-Control'] if digest headers['Cache-Control'] = @cache_control if @cache_control else headers['Cache-Control'] = @no_cache_control if @no_cache_control end end [status, headers, body] end
  44. @panthomakos def call(env) status, headers, body = @app.call(env) if etag_status?(status)

    && etag_body?(body) && !skip_caching?(headers) digest, body = digest_body(body) headers['ETag'] = %("#{digest}") if digest end unless headers['Cache-Control'] if digest headers['Cache-Control'] = @cache_control if @cache_control else headers['Cache-Control'] = @no_cache_control if @no_cache_control end end [status, headers, body] end
  45. @panthomakos private def digest_body(body) parts = [] body.each { |part|

    parts << part } string_body = parts.join digest = Digest::MD5.hexdigest(string_body) unless string_body.empty? [digest, parts] end
  46. @panthomakos private def digest_body(body) parts = [] body.each { |part|

    parts << part } string_body = parts.join digest = Digest::MD5.hexdigest(string_body) unless string_body.empty? [digest, parts] end
  47. @panthomakos private def digest_body(body) parts = [] body.each { |part|

    parts << part } string_body = parts.join digest = Digest::MD5.hexdigest(string_body) unless string_body.empty? [digest, parts] end
  48. @panthomakos def call(env) status, headers, body = @app.call(env) if etag_status?(status)

    && etag_body?(body) && !skip_caching?(headers) digest, body = digest_body(body) headers['ETag'] = %("#{digest}") if digest end unless headers['Cache-Control'] if digest headers['Cache-Control'] = @cache_control if @cache_control else headers['Cache-Control'] = @no_cache_control if @no_cache_control end end [status, headers, body] end
  49. @panthomakos def call(env) status, headers, body = @app.call(env) # if

    etag_status?(status) && etag_body?(body) && !skip_caching?(headers) # digest, body = digest_body(body) # headers['ETag'] = %("#{digest}") if digest # end unless headers['Cache-Control'] if digest headers['Cache-Control'] = @cache_control if @cache_control else headers['Cache-Control'] = @no_cache_control if @no_cache_control end end [status, headers, body] end
  50. @panthomakos def call(env) status, headers, body = @app.call(env) if etag_status?(status)

    && etag_body?(body) && !skip_caching?(headers) original_body = body digest, new_body = digest_body(body) body = Rack::BodyProxy.new(new_body) do original_body.close if original_body.respond_to?(:close) end headers[ETAG_STRING] = %(W/"#{digest}") if digest end unless headers[CACHE_CONTROL] if digest headers[CACHE_CONTROL] = @cache_control if @cache_control else headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control end end [status, headers, body] end
  51. @panthomakos def call(env) status, headers, body = @app.call(env) if etag_status?(status)

    && etag_body?(body) && !skip_caching?(headers) original_body = body digest, new_body = digest_body(body) body = Rack::BodyProxy.new(new_body) do original_body.close if original_body.respond_to?(:close) end headers[ETAG_STRING] = %(W/"#{digest}") if digest end unless headers[CACHE_CONTROL] if digest headers[CACHE_CONTROL] = @cache_control if @cache_control else headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control end end [status, headers, body] end
  52. @panthomakos module Strava class EntityMapMiddleware def initialize(app) @app = app

    end def call(env) status, headers, body = @app.call(env) [status, headers, BodyProxy.new(body)] end end end
  53. @panthomakos module Strava class EntityMapMiddleware def initialize(app) @app = app

    end def call(env) ::Strava::EntityMap.clear! @app.call(env) end end end
  54. @panthomakos class MyTimer def initialize(app) @app = app end def

    call(env) start = ::Timer.cputime response = @app.call(env) finish = ::Timer.cputime puts "Response Time: #{finish - start}ms" response end end
  55. @panthomakos class MyTimer def initialize(app) @app = app end def

    call(env) start = ::Timer.cputime response = @app.call(env) finish = ::Timer.cputime puts "Response Time: #{finish - start}ms" response end end
  56. @panthomakos class MyTimer def initialize(app) @app = app end def

    call(env) start = ::Timer.cputime response = @app.call(env) finish = ::Timer.cputime puts "Response Time: #{finish - start}ms" response end end
  57. @panthomakos class MyTimer def before(_request) @start = Timer.cputime end def

    after(_response) @finish = Timer.cputime puts "Response Time: #{@finish - @start}ms" end end