module Thin # The utterly famous Thin HTTP server. # It listens for incoming requests through a given +backend+ # and forwards all requests to +app+. # # == TCP server # Create a new TCP server bound to host:port 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 / # it will be assumed to be a UNIX socket. # # Thin::Server.start('/tmp/thin.sock', 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 passing it as the :backend option. # # Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend) # # == Rack application (+app+) # All requests will be processed through +app+, which must be a valid Rack adapter. # A valid Rack adapter (application) must respond to call(env#Hash) and # return an array of [status, headers, body]. # # == Building an app in place # If a block is passed, a Rack::Builder 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 # # == Controlling with signals # * INT and TERM: Force shutdown (see Server#stop!) # * TERM & QUIT calls +stop+ to shutdown gracefully. # * HUP calls +restart+ to ... surprise, restart! # * USR1 reopen log files. # Signals are processed at one second intervals. # Disable signals by passing :signals => false. # class Server include Logging include Daemonizable extend Forwardable # Default values DEFAULT_TIMEOUT = 30 #sec DEFAULT_HOST = '0.0.0.0' DEFAULT_PORT = 3000 DEFAULT_MAXIMUM_CONNECTIONS = 1024 DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 100 # Application (Rack adapter) called with the request that produces the response. attr_accessor :app # A tag that will show in the process listing attr_accessor :tag # 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 connections that can be persistent at the same time. # Most browsers never close the connection so most of the time they are closed # when the timeout occurs. If we don't control the number of persistent connections, # it would be very easy to overflow the server for a DoS attack. def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections= # Allow using threads in the backend. def_delegators :backend, :threaded?, :threaded=, :threadpool_size, :threadpool_size= # Allow using SSL in the backend. def_delegators :backend, :ssl?, :ssl=, :ssl_options= # 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 # Disable the use of epoll under Linux def_delegators :backend, :no_epoll, :no_epoll= def initialize(*args, &block) host, port, options = DEFAULT_HOST, DEFAULT_PORT, {} # Guess each parameter by its type so they can be # received in any order. args.each do |arg| case arg when 0.class, /^\d+$/ then port = arg.to_i when String then host = arg when Hash then options = arg else @app = arg if arg.respond_to?(:call) end end # Set tag if needed self.tag = options[:tag] # Try to intelligently select which backend to use. @backend = select_backend(host, port, options) load_cgi_multipart_eof_fix @backend.server = self # Set defaults @backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS @backend.timeout = options[: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? @setup_signals = options[:signals] != false 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. def start raise ArgumentError, 'app required' unless @app log_info "Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})" log_debug "Debugging ON" trace "Tracing ON" log_info "Maximum connections set to #{@backend.maximum_connections}" log_info "Listening on #{@backend}, CTRL+C to stop" @backend.start { setup_signals if @setup_signals } 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 waits for all current connections to finish. # Calling twice is the equivalent of calling stop!. def stop if running? @backend.stop unless @backend.empty? log_info "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! if @backend.started_reactor? log_info "Stopping ..." else log_info "Stopping Thin ..." log_info "Thin was started inside an existing EventMachine.run block." log_info "Call `EventMachine.stop` to stop the reactor and quit the process." end @backend.stop! end # == Reopen log file. # Reopen the log file and redirect STDOUT and STDERR to it. def reopen_log return unless log_file file = File.expand_path(log_file) log_info "Reopening log file: #{file}" Daemonize.redirect_io(file) end # == Configure the server # The process might need to have superuser privilege to configure # server with optimal options. def config @backend.config end # Name of the server and type of backend used. # This is also the name of the process in which Thin is running as a daemon. def name "thin server (#{@backend})" + (tag ? " [#{tag}]" : "") 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 # Queue up signals so they are processed in non-trap context # using a EM timer. @signal_queue ||= [] %w( INT TERM ).each do |signal| trap(signal) { @signal_queue.push signal } end # *nix only signals %w( QUIT HUP USR1 ).each do |signal| trap(signal) { @signal_queue.push signal } end unless Thin.win? # Signals are processed at one second intervals. @signal_timer ||= EM.add_periodic_timer(1) { handle_signals } end def handle_signals case @signal_queue.shift when 'INT' stop! when 'TERM', 'QUIT' stop when 'HUP' restart when 'USR1' reopen_log end EM.next_tick { handle_signals } unless @signal_queue.empty? end def select_backend(host, port, options) case when options.has_key?(:backend) raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class) options[:backend].new(host, port, options) when options.has_key?(:swiftiply) Backends::SwiftiplyClient.new(host, port, options) when host.include?('/') Backends::UnixServer.new(host) else Backends::TcpServer.new(host, port) end end # Taken from Mongrel cgi_multipart_eof_fix # Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it. 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_error "Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:" log_error "gem install cgi_multipart_eof_fix" end end end end end