The title is pretty self-explanatory. Is there any way to get the headers (except for Rack::Request.env[])?
 
    
    - 158,662
- 42
- 215
- 303
 
    
    - 2,082
- 3
- 17
- 28
3 Answers
The HTTP headers are available in the Rack environment passed to your app:
HTTP_Variables: Variables corresponding to the client-supplied HTTP request headers (i.e., variables whose names begin with HTTP_). The presence or absence of these variables should correspond with the presence or absence of the appropriate HTTP header in the request.
So the HTTP headers are prefixed with "HTTP_" and added to the hash.
Here's a little program that extracts and displays them:
require 'rack'
app = Proc.new do |env|
  headers = env.select {|k,v| k.start_with? 'HTTP_'}
    .collect {|key, val| [key.sub(/^HTTP_/, ''), val]}
    .collect {|key, val| "#{key}: #{val}<br>"}
    .sort
  [200, {'Content-Type' => 'text/html'}, headers]
end
Rack::Server.start :app => app, :Port => 8080
When I run this, in addition to the HTTP headers as shown by Chrome or Firefox, there is a "VERSION: HTPP/1.1" (i.e. an entry with key "HTTP_VERSION" and value "HTTP/1.1" is being added to the env hash).
 
    
    - 78,533
- 8
- 163
- 197
- 
                    3Ah, so it's basically `env` anyway :). What I dislike are the upcased names with some chars replaced. Well, I guess I will have to get away with it.... – PJK Jun 11 '11 at 21:34
- 
                    @PJK well the names should be case insensitive anyway: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2. What characters are being replaced? Are you trying to use characters from outside the ASCII chracter set? Header names should be ASCII only. – matt Jun 11 '11 at 22:16
- 
                    2I know, it is just a matter of convenience... For instance, X-Build becomes HTTP_X_BUILD, which means X_Build and X-BUILD should be equivalent but (I've been told that) browsers differentiate between these two alternatives. – PJK Jun 11 '11 at 22:29
- 
                    3@PJK I see: `-` is being changed to `_`. I guess that's to remain compatible with CGI (an environment variable can't contain `-`). But the _response_ headers shouldn't be affected. – matt Jun 11 '11 at 23:47
- 
                    Link to Rack environment documentation that's not broken: https://github.com/rack/rack/blob/master/SPEC.rdoc#the-environment- – salomvary Jan 14 '21 at 11:36
Based on @matt's answer, but this really gives you the request headers in a hash as requested in the question:
headers = Hash[*env.select {|k,v| k.start_with? 'HTTP_'}
  .collect {|k,v| [k.sub(/^HTTP_/, ''), v]}
  .collect {|k,v| [k.split('_').collect(&:capitalize).join('-'), v]}
  .sort
  .flatten]
Depending on what key convention you prefer you might want to use something else instead of :capitalize.
 
    
    - 18,880
- 12
- 68
- 105
Like @Gavriel's answer, but using transform_keys (simpler):
class Request
  def headers
    env.select { |k,v| k.start_with? 'HTTP_'}.
      transform_keys { |k| k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-') }
  end
end
You can even make it so lookups still work even if the case is different:
  def headers
    env.
      select { |k,v| k.start_with? 'HTTP_'}.
      transform_keys { |k| k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-') }.
      sort.to_h.
      tap do |headers|
        headers.define_singleton_method :[] do |k|
          super(k.split(/[-_]/).map(&:capitalize).join('-'))
        end
      end
  end
So for example, even if headers normalizes the keys so it returns this:
{
  Dnt: '1',
  Etag: 'W/"ec4454af5ae1bacff1afc5a06a2133f4"',
  'X-Xss-Protection': '1; mode=block',
}
you can still look up headers using the more natural/common names for these headers:
headers['DNT']
headers['ETag']
headers['X-XSS-Protection']
 
    
    - 9,191
- 6
- 60
- 60
- 
                    in your example, shouldn't it be `'Dnt': '1'` instead of `Dnt: '1'`? – Ron Klein May 21 '19 at 06:13
- 
                    
- 
                    @MarlinPierce Well it is syntactically correct Ruby but it's not what the code produces. @RonKlein is right, it should be `'Dnt': '1',` The keys here are strings from start to end. Capitalized literals denote constants in Ruby. – Arnaud Meuret Nov 05 '19 at 10:42
- 
                    @ArnaudMeuret Now, I think you are mixing up Constants and Literals. In ruby, `'Dnt':` resolves to the symbol :Dnt. If you want string keys, you need `{ 'Dnt' => '1' }`. This will show you that `'Dnt':` is a symbol, `{ 'Dnt': '1' }.each_pair { |key, value| puts key.inspect }`. – Marlin Pierce Nov 06 '19 at 18:49
- 
                    @ArnaudMeuret another way to see this, is that `Dnt='Knock';{ 'Dnt': 1, 'Dnt' => 2, Dnt => 3 }` evaluates to `{:Dnt=>1, "Dnt"=>2, "Knock"=>3}`. – Marlin Pierce Nov 06 '19 at 18:56
- 
                    @MarlinPierce Mmm I'm not. But, with the help of the faulty syntax highlighter, I did overlook that Tyler used the : mapping and misstook it for =>. My point remains that I don't see where his function produces these symbol keys, which you can not look up using strings. – Arnaud Meuret Nov 08 '19 at 03:34
- 
                    now that Rack is adopting all-downcase headers, I believe it would be a good idea to remove the "capitalize" from your code – Everton J. Carpes Sep 23 '22 at 14:59