I'm hoping someone can clear something up for me. I'm using Rails 2.3.5, and I can access request headers in a controller action like this:
def index if request.headers['...'] == '...' ... end end
Or something similar. request.headers is an instance of ActionController::Http::Headers which appears to be a Hash. I would expect, therefore, that headers are keyed on the name I send. If I send a request however, with an Authorization header, like so:
curl -H 'Authorization: OAuth realm="MyRealm",...' http://app/path
The following code in the action returns false:
if request.headers.include?('Authorization') ...
Whereas the following echos out the value I send in the header:
render :text => request.headers['Authorization']
The following check returns true, interestingly enough:
if request.headers.include?('HTTP_AUTHORIZATION') ...
And similarly, the following echoes out the value I send in the header:
render :text => request.headers['HTTP_AUTHORIZATION']
Seems like there is some magic happening that I'm unaware of. I'm completely confused as to why checking for the key 'Authorization' fails, but rendering the value of request.headers['Authorization'] succeeds. I'm also confused as to where 'HTTP_AUTHORIZATION' is coming from as that is not the name of the header I'm sending with the request. Anyone know what's going on exactly?
You are correct - the headers
method of ActionController::Request
returns an instance of ActionController::Http::Headers
, which is inherits from Hash. If we crack open the source, we see this:
class Headers < ::Hash extend ActiveSupport::Memoizable def initialize(*args) if args.size == 1 && args[0].is_a?(Hash) super() update(args[0]) else super end end def [](header_name) if include?(header_name) super else super(env_name(header_name)) end end private # Converts a HTTP header name to an environment variable name. def env_name(header_name) "HTTP_#{header_name.upcase.gsub(/-/, '_')}" end memoize :env_name end
So when accessing the Hash via []
, there's a second check to see if value from env_name
(which just upcases the key and prepends HTTP_
) exists.
This is why you can't get a true value from request.headers.include?('Authorization')
-- include?
is not overridden in the subclass to check for both the normal and upcased version of the header. I imagine you could follow suit and implement it yourself like this:
module ActionController module Http class Headers < ::Hash def include?(header_name) self[header_name].present? end end end end
Throw that into lib/extensions/action_controller.rb
or something, require it in environment.rb
if necessary, and you should be good to go. I'd recommend just modifying your controller code to use []
and present?
to do the check, though :)
The reason that the headers are upcased and prefixed with HTTP_
, I believe, stems from Rack, Rails' HTTP middleware. It probably does this to remain impartial about case, additionally prepending HTTP_
to avoid conflicts with other non-header environment stuff that comes in.
So, yes, a bit magical, but not too hard to understand after glancing at the source, which I'd always recommend :) Rails has some very nice source that I've learned a lot from over the years.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With