Skip to content

Instantly share code, notes, and snippets.

@mudge
Last active March 24, 2023 05:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mudge/acde31a5319726b9fdba419ffe7f5bcb to your computer and use it in GitHub Desktop.
Save mudge/acde31a5319726b9fdba419ffe7f5bcb to your computer and use it in GitHub Desktop.
Instruct Rails to respect the Accept header even if it contains a wildcard
class ApiController < ApplicationController
before_action :only_respect_accept_header
private
# By default, Rails will ignore the Accept header if it contains a wildcard
# and assume the client wants HTML (or JS if using XMLHttpRequest). See
# https://github.com/rails/rails/blob/a807a4f4f95798616a2a85856f77fdfc48da4832/actionpack/lib/action_dispatch/http/mime_negotiation.rb#L171-L173
#
# If you don't expect your clients to be browsers, we want to override this
# and only set the request formats from the Accept header, falling back to
# the wildcard if it is not present (and therefore respecting our
# priorities).
#
# Note we have to filter down the MIME types to ones Rails understands
# otherwise it will raise an error when attempting to set formats for the
# lookup context.
def only_respect_accept_header
request.set_header(
"action_dispatch.request.formats",
requested_mime_types.select { |type| type.symbol || type.ref == "*/*" }
)
end
def requested_mime_types
Mime::Type.parse(request.get_header("HTTP_ACCEPT").to_s).presence || [Mime::ALL]
end
end
require "rails_helper"
RSpec.describe ApiController do
controller do
def index
respond_to do |format|
format.text { render plain: "Plain text" }
format.json { render json: "JSON" }
end
end
def show
respond_to do |format|
format.text { render plain: "Plain text" }
format.any { render plain: "Fallback" }
end
end
end
it "returns the first format when given no Accept header" do
get :index
expect(response.media_type).to eq("text/plain")
end
it "returns the first format when given a wildcard Accept header" do
request.headers["Accept"] = "*/*"
get :index
expect(response.media_type).to eq("text/plain")
end
it "returns the first format when given Safari and Chrome's default Accept header" do
request.headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
get :index
expect(response.media_type).to eq("text/plain")
end
it "returns a text/plain response when explicitly requested" do
request.headers["Accept"] = "text/plain"
get :index
expect(response.media_type).to eq("text/plain")
end
it "returns an application/json response when explicitly requested" do
request.headers["Accept"] = "application/json"
get :index
expect(response.media_type).to eq("application/json")
end
it "raises an error if given an unsupported MIME type" do
request.headers["Accept"] = "image/webp"
expect { get :index }.to raise_error(ActionController::UnknownFormat)
end
it "does not raise an error when given an unsupported MIME type if an 'any' format is present" do
request.headers["Accept"] = "image/webp"
get :show, params: { id: 1 }
expect(response.body).to eq("Fallback")
end
it "raises an error when given an invalid MIME type" do
request.headers["Accept"] = "not a valid MIME type"
expect { get :index }.to raise_error(Mime::Type::InvalidMimeType)
end
it "raises an error when given an invalid MIME type even if an 'any' format is present" do
request.headers["Accept"] = "not a valid MIME type"
expect { get :show, params: { id: 1 } }.to raise_error(Mime::Type::InvalidMimeType)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment