Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.3k views
in Technique[技术] by (71.8m points)

http - Authorization header in Ruby on Rails accessed with key HTTP_AUTHORIZATION instead of Authorization?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...