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)
Thu Feb 21 11:43:25 -0800 2008
commit  adb74dfa1540eb56a88afaf692bdba40911d59ee
tree    2d3eb97e05543449bc076880f3c98798f606a5f6
parent  e9b45d9dcaa97e2d9f42d7fb9f1385ed61738206
thin / lib / thin / server.rb
100644 222 lines (188 sloc) 7.658 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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
module Thin
  # The uterly famous Thin HTTP server.
  # It listen for incoming request through a given connector
  # 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 connector
  # You can implement your own way to connect the server to its client by creating your
  # own Thin::Connectors::Connector class and pass it as the first argument.
  #
  # connector = Thin::Connectors::MyFancyConnector.new('galaxy://faraway:1345')
  # Thin::Server.start(connector, 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
    
    # Connector handling the connections to the clients.
    attr_accessor :connector
    
    # Maximum number of file or socket descriptors that the server may open.
    attr_accessor :maximum_connections
    
    # Maximum number of seconds for incoming data to arrive before the connection
    # is dropped.
    def_delegators :@connector, :timeout, :timeout=
    
    # 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 :@connector, :maximum_persistent_connections, :maximum_persistent_connections=
    
    # Address and port on which the server is listening for connections.
    def_delegators :@connector, :host, :port
    
    # UNIX domain socket on which the server is listening for connections.
    def_delegator :@connector, :socket
    
    def initialize(host_or_socket_or_connector, port=DEFAULT_PORT, app=nil, &block)
      # Try to intelligently select which connector to use.
      @connector = case
      when host_or_socket_or_connector.is_a?(Connectors::Connector)
        host_or_socket_or_connector
      when host_or_socket_or_connector.include?('/')
        Connectors::UnixServer.new(host_or_socket_or_connector)
      else
        Connectors::TcpServer.new(host_or_socket_or_connector, port.to_i)
      end
 
      @app = app
      @connector.server = self
      
      # Set defaults
      @maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
      @connector.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
      @connector.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
            
      # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
      EventMachine.epoll
      
      log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
      debug ">> Debugging ON"
      trace ">> Tracing ON"
            
      log ">> Listening on #{@connector}, CTRL+C to stop"
      
      @running = true
      EventMachine.run { @connector.connect }
    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
        @running = false
        
        # Do not accept anymore connection
        @connector.disconnect
        
        unless wait_for_connections_and_stop
          # Still some connections running, schedule a check later
          EventMachine.add_periodic_timer(1) { wait_for_connections_and_stop }
        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 ..."
 
      @connector.close_connections
      EventMachine.stop
 
      @connector.close
    end
        
    def name
      "thin server (#{@connector})"
    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?
      @running
    end
    
    # Set the maximum number of socket descriptors that the server may open.
    # The process needs to have required privilege to set it higher the 1024 on
    # some systems.
    def set_descriptor_table_size!
      return 0 if Thin.win? # Not supported on Windows
      
      requested_maximum_connections = @maximum_connections
      @maximum_connections = EventMachine.set_descriptor_table_size(requested_maximum_connections)
 
      log ">> Setting maximum connections to #{@maximum_connections}"
      if @maximum_connections < requested_maximum_connections
        log "!! Maximum connections smaller then requested, " +
            "run with sudo to set higher"
      end
 
      @maximum_connections
    end
    
    protected
      def wait_for_connections_and_stop
        if @connector.empty?
          stop!
          true
        else
          log ">> Waiting for #{@connector.size} connection(s) to finish, can take up to #{timeout} sec, CTRL+C to stop now"
          false
        end
      end
      
      def setup_signals
        trap('QUIT') { stop } unless Thin.win?
        trap('INT') { stop! }
        trap('TERM') { stop! }
      end
  end
end