Posted on • Originally published atblog.arkency.com
Explaining Rack — desugaring Rack::Builder DSL
Yesterday Iwrote a post highlighting Basic Auth and how can we protect Rack applications mounted in Rails with it.
Today when discussing some ideas from this post with my colleague, our focus immediately shifted toRack::Builder
.
On one hand Rack interface is simple, fairly constrained and well described inspec. And ships with alinter to help your Rack apps and middleware pass this compliance.
A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values:The status,the headers, andthe body.
classHelloWorlddefcall(env)[200,{"Content-Type"=>"text/plain"},["Hello world!"]]endend
On the other hand your first exposure to Rack is usually viaconfig.ru
in Rails application:
# This file is used by Rack-based servers to start the application.require_relative"config/environment"runRails.application
Behind the scenes, this fileis eventually passed toRack::Builder
. It is a convenient DSL to compose Rack application out of other Rack applications. Inyesterday's blogpost we've seen it being used directly:
Rack::Builder.newdouseRack::Auth::Basicdo|username,password|ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username),::Digest::SHA256.hexdigest(ENV.fetch("DEV_UI_USERNAME")))&ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password),::Digest::SHA256.hexdigest(ENV.fetch("DEV_UI_PASSWORD")))endrunSidekiq::Webend
Whether you useRack::Builder
directly or viarackup files, you're immediately associating Rack with theuse
andrun
DSL.
And that triggered an honest question from my colleague — how does this DSL relate to that rather simple Rack interface?
De-sugaring Rack::Builder DSL
To add even more nuance, some Rack apps are called amiddleware. What is a middleware? In simple words — it's a Rack application that wraps another Rack application. It may affect the input passed to the wrapped app. Or it may affect the output if it.
An example of amiddleware that makes everything sound more dramatic:
classDramatizedefinitialize(app)@app=appenddefcall(env)status,headers,body=@app.call(env)[status,headers,body.map{|x|"#{x}111one!1"}]endend
A composition of such middleware and our previous sampleHelloWorld
application withconfig.ru
would look like this:
# config.ruclassHelloWorld# omitted for brevityendclassDramatize# omitted for brevityenduseDramatizerunHelloWorld.new
When executed, it would return very dramatic greeting:
$ bundle exec rackup config.ru* Listening on http://127.0.0.1:9292* Listening on http://[::1]:9292Use Ctrl-C to stop$ curl localhost:9292 Hello world!111one!1⏎
Now back to the question that started it all:
How does this DSL relate to that rather simple Rack interface?
The last example of composition viaRack::Builder
can be rewritten to avoid some of the DSL:
# config.rurunDramatize.new(HelloWorld.new)
A singlerun
is needed to tell a Ruby application server what is our Rack application that we'd like to run. The use ofuse
is on the other hand just optional.
If this post got you curious on Rack, a fun way to learn more about it is to check the code of each middleware powering your Rails application:
$ bin/rails middlewareuse Webpacker::DevServerProxyuse Honeybadger::Rack::UserInformeruse Honeybadger::Rack::UserFeedbackuse Honeybadger::Rack::ErrorNotifieruse Rack::Corsuse ActionDispatch::HostAuthorizationuse Rack::Sendfileuse ActionDispatch::Staticuse ActionDispatch::Executoruse ActiveSupport::Cache::Strategy::LocalCache::Middlewareuse Rack::Runtimeuse Rack::MethodOverrideuse ActionDispatch::RequestIduse ActionDispatch::RemoteIpuse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::ActionableExceptionsuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActionDispatch::Cookiesuse ActionDispatch::Session::CookieStoreuse ActionDispatch::Flashuse ActionDispatch::ContentSecurityPolicy::Middlewareuse ActionDispatch::PermissionsPolicy::Middlewareuse Rack::Headuse Rack::ConditionalGetuse Rack::ETaguse Rack::TempfileReaperuse Warden::Manageruse Rack::Deflateruse RailsEventStore::Middlewarerun MyApp::Application.routes
Happy learning!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse