public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
macournoyer (author)
Sun Mar 02 11:32:21 -0800 2008
commit  4c30c781a26b14bd7038646a6c258f57cb91c1f3
tree    8d363642dfc6baf12dd4273676e62c684b26c098
parent  3380e9a2bdafb94b75b766255e4cd00fb4eb0046
thin / lib / thin / server.rb
100644 207 lines (178 sloc) 7.104 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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
module Thin
  # The uterly famous Thin HTTP server.
  # It listen for incoming request through a given backend
  # and forward all request to +app+.
  #
  # == TCP server
  # Create a new TCP server on bound to <tt>host:port</tt> by specifiying +host+
  # and +port+ as the first 2 arguments.
  #
  # Thin::Server.start('0.0.0.0', 3000, app)
  #
  # == UNIX domain server
  # Create a new UNIX domain socket bound to +socket+ file by specifiying a filename
  # as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a <tt>/</tt>
  # it will be assumed to be a UNIX socket.
  #
  # Thin::Server.start('/tmp/thin.sock', nil, app)
  #
  # == Using a custom backend
  # You can implement your own way to connect the server to its client by creating your
  # own Backend class and pass it as the first argument.
  #
  # backend = Thin::Backends::MyFancyBackend.new('galaxy://faraway:1345')
  # Thin::Server.start(backend, nil, app)
  #
  # == Rack application (+app+)
  # All requests will be processed through +app+ that must be a valid Rack adapter.
  # A valid Rack adapter (application) must respond to <tt>call(env#Hash)</tt> and
  # return an array of <tt>[status, headers, body]</tt>.
  #
  # == Building an app in place
  # If a block is passed, a <tt>Rack::Builder</tt> instance
  # will be passed to build the +app+. So you can do cool stuff like this:
  #
  # Thin::Server.start('0.0.0.0', 3000) do
  # use Rack::CommonLogger
  # use Rack::ShowExceptions
  # map "/lobster" do
  # use Rack::Lint
  # run Rack::Lobster.new
  # end
  # end
  #
  class Server
    include Logging
    include Daemonizable
    extend Forwardable
    
    # Default values
    DEFAULT_TIMEOUT = 30 #sec
    DEFAULT_PORT = 3000
    DEFAULT_MAXIMUM_CONNECTIONS = 1024
    DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 512
        
    # Application (Rack adapter) called with the request that produces the response.
    attr_accessor :app
    
    # Backend handling the connections to the clients.
    attr_accessor :backend
    
    # Maximum number of seconds for incoming data to arrive before the connection
    # is dropped.
    def_delegators :@backend, :timeout, :timeout=
    
    # Maximum number of file or socket descriptors that the server may open.
    def_delegators :@backend, :maximum_connections, :maximum_connections=
    
    # Maximum number of connection that can be persistent at the same time.
    # Most browser never close the connection so most of the time they are closed
    # when the timeout occur. If we don't control the number of persistent connection,
    # if would be very easy to overflow the server for a DoS attack.
    def_delegators :@backend, :maximum_persistent_connections, :maximum_persistent_connections=
    
    # Address and port on which the server is listening for connections.
    def_delegators :@backend, :host, :port
    
    # UNIX domain socket on which the server is listening for connections.
    def_delegator :@backend, :socket
    
    def initialize(host_or_socket_or_backend, port=DEFAULT_PORT, app=nil, &block)
      # Try to intelligently select which backend to use.
      @backend = case
      when host_or_socket_or_backend.is_a?(Backends::Base)
        host_or_socket_or_backend
      when host_or_socket_or_backend.include?('/')
        Backends::UnixServer.new(host_or_socket_or_backend)
      else
        Backends::TcpServer.new(host_or_socket_or_backend, port.to_i)
      end
      
      load_cgi_multipart_eof_fix
 
      @app = app
      @backend.server = self
      
      # Set defaults
      @backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
      @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
      @backend.timeout = DEFAULT_TIMEOUT
      
      # Allow using Rack builder as a block
      @app = Rack::Builder.new(&block).to_app if block
      
      # If in debug mode, wrap in logger adapter
      @app = Rack::CommonLogger.new(@app) if Logging.debug?
    end
    
    # Lil' shortcut to turn this:
    #
    # Server.new(...).start
    #
    # into this:
    #
    # Server.start(...)
    #
    def self.start(*args, &block)
      new(*args, &block).start!
    end
        
    # Start the server and listen for connections.
    # Also register signals:
    # * INT calls +stop+ to shutdown gracefully.
    # * TERM calls <tt>stop!</tt> to force shutdown.
    def start
      raise ArgumentError, 'app required' unless @app
      
      setup_signals
            
      log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
      debug ">> Debugging ON"
      trace ">> Tracing ON"
      
      log ">> Maximum connections set to #{@backend.maximum_connections}"
      log ">> Listening on #{@backend}, CTRL+C to stop"
      
      @backend.start
    end
    alias :start! :start
    
    # == Gracefull shutdown
    # Stops the server after processing all current connections.
    # As soon as this method is called, the server stops accepting
    # new requests and wait for all current connections to finish.
    # Calling twice is the equivalent of calling <tt>stop!</tt>.
    def stop
      if running?
        @backend.stop
        unless @backend.empty?
          log ">> Waiting for #{@backend.size} connection(s) to finish, " +
                 "can take up to #{timeout} sec, CTRL+C to stop now"
        end
      else
        stop!
      end
    end
    
    # == Force shutdown
    # Stops the server closing all current connections right away.
    # This doesn't wait for connection to finish their work and send data.
    # All current requests will be dropped.
    def stop!
      log ">> Stopping ..."
 
      @backend.stop!
    end
    
    # == Configure the server
    # The process might need to have superuser privilege to set configure
    # server with optimal options.
    def config
      @backend.config
    end
        
    def name
      "thin server (#{@backend})"
    end
    alias :to_s :name
    
    # Return +true+ if the server is running and ready to receive requests.
    # Note that the server might still be running and return +false+ when
    # shuting down and waiting for active connections to complete.
    def running?
      @backend.running?
    end
    
    protected
      def setup_signals
        trap('QUIT') { stop } unless Thin.win?
        trap('INT') { stop! }
        trap('TERM') { stop! }
      end
      
      # Taken from Mongrel cgi_multipart_eof_fix
      def load_cgi_multipart_eof_fix
        version = RUBY_VERSION.split('.').map { |i| i.to_i }
        
        if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
          begin
            require 'cgi_multipart_eof_fix'
          rescue LoadError
            log "!! Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
            log " gem install cgi_multipart_eof_fix"
          end
        end
      end
  end
end