public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
Click here to lend your support to: thin and make a donation at www.pledgie.com !
thin / lib / rack / adapter / rails.rb
100644 162 lines (125 sloc) 5.335 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
require 'cgi'
 
# Adapter to run a Rails app with any supported Rack handler.
# By default it will try to load the Rails application in the
# current directory in the development environment.
#
# Options:
# root: Root directory of the Rails app
# environment: Rails environment to run in (development [default], production or test)
# prefix: Set the relative URL root.
#
# Based on http://fuzed.rubyforge.org/ Rails adapter
module Rack
  module Adapter
    class Rails
      FILE_METHODS = %w(GET HEAD).freeze
      
      def initialize(options={})
        @root = options[:root] || Dir.pwd
        @env = options[:environment] || 'development'
        @prefix = options[:prefix]
        
        load_application
        
        @file_server = Rack::File.new(::File.join(RAILS_ROOT, "public"))
      end
      
      def load_application
        ENV['RAILS_ENV'] = @env
 
        require "#{@root}/config/environment"
        require 'dispatcher'
        
        ActionController::AbstractRequest.relative_url_root = @prefix if @prefix
      end
      
      # TODO refactor this in File#can_serve?(path) ??
      def file_exist?(path)
        full_path = ::File.join(@file_server.root, Utils.unescape(path))
        ::File.file?(full_path) && ::File.readable?(full_path)
      end
      
      def serve_file(env)
        @file_server.call(env)
      end
      
      def serve_rails(env)
        request = Request.new(env)
        response = Response.new
        
        session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
        cgi = CGIWrapper.new(request, response)
    
        Dispatcher.dispatch(cgi, session_options, response)
 
        response.finish
      end
      
      def call(env)
        path = env['PATH_INFO'].chomp('/')
        method = env['REQUEST_METHOD']
        cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension
        
        if FILE_METHODS.include?(method)
          if file_exist?(path) # Serve the file if it's there
            return serve_file(env)
          elsif file_exist?(cached_path) # Serve the page cache if it's there
            env['PATH_INFO'] = cached_path
            return serve_file(env)
          end
        end
        
        # No static file, let Rails handle it
        serve_rails(env)
      end
    
      protected
        
        class CGIWrapper < ::CGI
          def initialize(request, response, *args)
            @request = request
            @response = response
            @args = *args
            @input = request.body
 
            super *args
          end
        
          def header(options = "text/html")
            if options.is_a?(String)
              @response['Content-Type'] = options unless @response['Content-Type']
            else
              @response['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
            
              @response['Content-Type'] = options.delete('type') || "text/html"
              @response['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
                        
              @response['Content-Language'] = options.delete('language') if options['language']
              @response['Expires'] = options.delete('expires') if options['expires']
 
              @response.status = options.delete('Status') if options['Status']
              
              # Convert 'cookie' header to 'Set-Cookie' headers.
              # Because Set-Cookie header can appear more the once in the response body,
              # we store it in a line break seperated string that will be translated to
              # multiple Set-Cookie header by the handler.
              if cookie = options.delete('cookie')
                cookies = []
                
                case cookie
                  when Array then cookie.each { |c| cookies << c.to_s }
                  when Hash then cookie.each { |_, c| cookies << c.to_s }
                  else cookies << cookie.to_s
                end
        
                @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
                
                @response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact.join("\n")
              end
              
              options.each { |k,v| @response[k] = v }
            end
            
            ""
          end
                        
          def params
            @params ||= @request.params
          end
        
          def cookies
            @request.cookies
          end
        
          def query_string
            @request.query_string
          end
          
          # Used to wrap the normal args variable used inside CGI.
          def args
            @args
          end
    
          # Used to wrap the normal env_table variable used inside CGI.
          def env_table
            @request.env
          end
    
          # Used to wrap the normal stdinput variable used inside CGI.
          def stdinput
            @input
          end
        
          def stdoutput
            STDERR.puts "stdoutput should not be used."
            @response.body
          end
      end
    end
  end
end