Rack

Rack provides a minimal, modular, and adaptable interface for building web applications. It wraps HTTP requests and responses in the simplest way possible, and unifies and distills the API for web servers, web frameworks, and middleware into a single method call.

The standardized Interface of Rack is common for all web applications and web servers. This interface expects a Ruby object (usually a class or proc) that responds to a single method call −

call(env)

where env is a hash containing the HTTP request data. The call() method must return an array with three elements −

[status_code, headers_hash, body_array]

Pure Rack Application

To begin with, let us build a pure Rack Application without Rails.

mkdir rackapp
cd rackapp

Save the following code in the config.ru file.

# config.ruclassAppdefcall(env)
  headers ={'Content-Type'=>'text/html'}
  response =['<h1>Greetings from Rack!!</h1>'][200, headers, response]endend
run App.new

This is a basic web application that returns a simple response. We shall use Puma the web server that Ruby on Rails ships with. Rack also works with servers such as Thin and Unicorn.

To run the application with Puma, use the following command −

puma config.ru

The server starts at port 9292.

***SIGUSR2not implemented, signal based restart unavailable!***SIGUSR1not implemented, signal based restart unavailable!***SIGHUPnot implemented, signal based logs reopening unavailable!Puma starting in single mode...*Puma version:6.6.0("Return to Forever")*Ruby version: ruby 3.3.7(2025-01-15 revision be31f993d7)[x64-mingw-ucrt]*Min threads:0*Max threads:5*Environment: development
*PID:12756*Listening on http://0.0.0.0:9292

Use Ctrl-C to stop

Open a browser and visit http://localhost:9292 to get the following output −

Rails on Rack1

Any Rack-compliant web server works well here, as it will always invoke a call method on an object (the Rack application) and serve the result of that method.

Rack allows application frameworks and web servers to communicate with each other, and replace each without changing the other. The advantage of Rack is that it provides a common protocol (or interface, or specification) that different web servers can use to talk to different web applications, without worrying about the internals of each.

Rails is built on top of Rack, so that every Rails application is also a Rack application. Rails uses a Rack-compatible interface to handle HTTP requests and responses.

You can add or customize Rack middleware in a Rails app through the config/application.rb or environment-specific config files.

Rack App in Rails

Let us now create a standalone Rack app and mount it inside Rails routes.

Assume that you have already created a Rails app.

rails new myapp

Enter the lib folder of the project file structure and open the following code as lib/hello_rack_app.rb –

classHelloRackAppdefcall(env)[200,{"Content-Type"=>"text/html"},["<h1>Greetings from Rack!!</h1>"]]endend

This is a simple Rack app as before. To mount it along with other Rails routes, join its path with other routes. Edit the config/routes.rb file of the project with the following code −

requireRails.root.join("lib/hello_rack_app")Rails.application.routes.draw do# Other Rails routes...
   root "index#index"
   mount HelloRackApp.new=>"/hello_rack"end

That’s it. Save the changes and run the Rails server. You now have mounted the Rack app on a Rails application. Visit http://localhost:3000/hello_rack to get the following output −

Rails on Rack2

Rack Middleware

Rack allows middleware components (e.g., logging, session handling, CORS handling, etc.) to be inserted into the request/response cycle. Middleware sits between the server and the application and can modify the request or response.

To define your Rack middleware, create a new class in lib/middleware folder that implements the call(env) method −

# lib/middleware/logger_middleware.rbclassLoggerMiddlewaredefinitialize(app)@app= app
   enddefcall(env)Rails.logger.info ">> Request received at: #{Time.now}"
	  status, headers, response [email protected](env)Rails.logger.info "<< Response status: #{status}"[status, headers, response]endend

Next, add it to the middleware stack −

# config/application.rb
config.autoload_paths <<Rails.root.join('lib/middleware')
config.middleware.use LoggerMiddleware

You can also use insert_before or insert_after to control its position relative to other middleware.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *