Working with HTTP Response in Rails

Working with HTTP Responses in Rails

In this post, we'll learn how to work with the response object in Rails controllers — from inspecting response bodies and headers to setting status codes and content types. This guide also covers key methods like body, status=, content_type, cookies, and more, with practical examples.

4 min read
This is the fourth post in my series on Rails Controllers.

HTTP is a request response protocol. The browser (client) sends an HTTP request to the server (Rails application), which parses that request and generates the HTTP response, typically an HTML page, and sends it to the browser. The browser then renders that HTML on the screen.

In the previous post on requests in Rails, we learned how you can work with the request object to access meaningful information on the incoming HTTP request. In this post, we'll take a look at how you can work with the outgoing response. While it’s less common to handle responses directly—since Rails manages most of that for you—there are situations where working with response objects is necessary.

Accessing the Response Object

In a Rails controller, the response object represents the HTTP response that your application is preparing to send back to the client (browser, API client, etc.). You can access the response in your controllers using the response object, which is an instance of the ActionDispatch::Response class.

class ProjectsController < ApplicationController
  def show
    klass = response.class  # => ActionDispatch::Response
    code = response.code    # => 200
    message = response.status_message # => "OK"

    # Get and set headers
    content_type_options = response.headers["x-content-type-options"]
    response.headers["Content-Type"] = "application/json"
  end
end

Like the request attribute, the response attribute comes from the ActionController::Metal class. A bunch of common and useful methods are delegated to the response object.

module ActionController
  class Metal
    attr_internal_reader :response

    delegate :headers, :status, :location, :content_type, :media_type, to: "@_response" 
    delegate :status=, :location=, :content_type=, to: "@_response"
  end
end

Whenever you call any of the above methods, like reading the status, accessing the content_type, or setting the location—Rails forwards those calls to the underlying response object.

💡
Remember that attr_internal_reader declares an attribute reader response backed by an internally-named instance variable, in our case @_response.

Now let's inspect some of the commonly used methods on the ActionDispatch::Response object.

What can you do with a Response?

In Rails controllers, the response object provides several useful methods for inspecting and manipulating the outgoing HTTP response.

In practice, you don't work directly with response that often. Instead, you use the various helper methods provided by Rails to manipulate the outgoing response. That said, it's good to be aware of the response API, and here're some of the commonly used methods.

  • body returns the response body as a string — this is the content that will be sent back to the client. Typically, this is the result of any render call made in the action.
render plain: "Hello, World!"
puts response.body # => "Hello, World!"
  • body= sets or overrides the response body manually. This is useful if you want to bypass Rails’ default rendering process and take full control of the response content.
response.body = "Custom response content"
  • charset returns the character set (encoding) of the response. HTML wants to know the encoding of the content you’re giving them, so we need to send that along. This is often UTF-8 for HTML or JSON responses.
puts response.charset # => "utf-8"
  • content_type returns the content type of the response (MIME type). This is set automatically by render and other response-generating methods, but you can also inspect or modify it directly.
render json: { message: "Hello" }
puts response.content_type # => "application/json"
  • content_type= sets the HTTP response’s content MIME type.
response.content_type = "application/xml"
  • cookies returns a Hash of cookies that will be sent with the response, in the form of name => value pairs. This can be useful if you need to inspect cookies set during the request lifecycle. We'll explore cookies in detail in a future post.
cookies[:user_id] = 42
puts response.cookies # => { "user_id" => "42" }
  • location returns the location of the response, useful when redirecting the response to another location.
redirect_to posts_path
puts response.location # => "http://localhost:3000/posts"
  • message returns the standard description for the current HTTP status code. This can be useful for logging or debugging purposes.
response.status = 404
puts response.message # => "Not Found"
  • send_file sends a file from the server as the response body. This method is often used when serving downloads like PDFs or images directly.
send_file Rails.root.join("public", "example.pdf")
  • status= Sets the HTTP status code.
response.status = 403
  • to_a Converts the response into a Rack-compatible array with three elements: [status, headers, body]. This is useful if you need to work directly at the Rack level (for example, in middleware or low-level response manipulation).
p response.to_a # => [200, { "Content-Type" => "text/html; charset=utf-8" }, ["Rendered page content"]]

Even though the response object exposes all these methods for you to manipulate the outgoing response, you should use them directly only if you have a specific, advanced need to bypass or enhance Rails' built-in helpers.

When to Use Above Methods Directly vs. When to Rely on Rails Helpers

In most Rails controllers, you should prefer higher-level helpers like render, redirect_to, head, and send_file for setting responses. These methods are designed to work with full response lifecycle in Rails, including content negotiation, layout rendering, error handling, and much more.

# Set body and content type in one step
render json: { message: "Success" }

# Set status and no content
head :no_content

# Redirect to another page - sets the Location header
redirect_to posts_path

Direct use of the response object is appropriate when:

  • You are writing low-level middleware-like logic inside a controller (rare).
  • You need to explicitly inspect or override specific response headers (such as adding a custom header for CORS or security purposes).
  • You want to debug or log raw response data during development or in a custom logging concern.
  • You are building a very customized response flow that does not fit standard rendering or redirection patterns.

In most standard Rails apps, you rarely work directly with response inside controllers. The response object is more frequently used in tests where you need to inspect the result of a controller action.

Hope that helps clarify things a little.


That's a wrap. I hope you found this article helpful and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.