public
Rubygem
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 / thin / controllers / controller.rb
100644 153 lines (128 sloc) 5.206 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
require 'yaml'
 
module Thin
  # Build and control one Thin server.
  # Hey Controller pattern is not only for web apps yo!
  module Controllers
    # Raised when a mandatory option is missing to run a command.
    class OptionRequired < RuntimeError
      def initialize(option)
        super("#{option} option required")
      end
    end
  
    # Controls a Thin server.
    # Allow to start, stop, restart and configure a single thin server.
    class Controller
      include Logging
    
      # Command line options passed to the thin script
      attr_accessor :options
    
      def initialize(options)
        @options = options
        
        if @options[:socket]
          @options.delete(:address)
          @options.delete(:port)
        end
      end
    
      def start
        # Select proper backend
        server = case
        when @options.has_key?(:socket)
          Server.new(@options[:socket])
        when @options.has_key?(:swiftiply)
          Server.new(Backends::SwiftiplyClient.new(@options[:address], @options[:port], @options[:swiftiply]))
        else
          Server.new(@options[:address], @options[:port])
        end
        
        # Set options
        server.pid_file = @options[:pid]
        server.log_file = @options[:log]
        server.timeout = @options[:timeout]
        server.maximum_connections = @options[:max_conns]
        server.maximum_persistent_connections = @options[:max_persistent_conns]
 
        # Detach the process, after this line the current process returns
        server.daemonize if @options[:daemonize]
 
        # +config+ must be called before changing privileges since it might require superuser power.
        server.config
        
        server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
 
        # If a Rack config file is specified we eval it inside a Rack::Builder block to create
        # a Rack adapter from it. DHH was hacker of the year a couple years ago so we default
        # to Rails adapter.
        if @options[:rackup]
          rackup_code = File.read(@options[:rackup])
          server.app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
        else
          server.app = Rack::Adapter::Rails.new(@options.merge(:root => @options[:chdir]))
        end
 
        # If a prefix is required, wrap in Rack URL mapper
        server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
 
        # If a stats URL is specified, wrap in Stats adapter
        server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
 
        # Register restart procedure which just start another process with same options,
        # so that's why this is done here.
        server.on_restart { Command.run(:start, @options) }
 
        server.start
      end
    
      def stop
        raise OptionRequired, :pid unless @options[:pid]
      
        tail_log(@options[:log]) do
          Server.kill(@options[:pid], @options[:timeout] || 60)
          wait_for_file :deletion, @options[:pid]
        end
      end
    
      def restart
        raise OptionRequired, :pid unless @options[:pid]
      
        tail_log(@options[:log]) do
          Server.restart(@options[:pid])
          wait_for_file :creation, @options[:pid]
        end
      end
    
      def config
        config_file = @options.delete(:config) || raise(OptionRequired, :config)
 
        # Stringify keys
        @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
 
        File.open(config_file, 'w') { |f| f << @options.to_yaml }
        log ">> Wrote configuration to #{config_file}"
      end
      
      protected
        # Wait for a pid file to either be created or deleted.
        def wait_for_file(state, file)
          case state
          when :creation then sleep 0.1 until File.exist?(file)
          when :deletion then sleep 0.1 while File.exist?(file)
          end
        end
        
        # Tail the log file of server +number+ during the execution of the block.
        def tail_log(log_file)
          if log_file
            tail_thread = tail(log_file)
            yield
            tail_thread.kill
          else
            yield
          end
        end
        
        # Acts like GNU tail command. Taken from Rails.
        def tail(file)
          cursor = File.exist?(file) ? File.size(file) : 0
          last_checked = Time.now
          tail_thread = Thread.new do
            Thread.pass until File.exist?(file)
            File.open(file, 'r') do |f|
              loop do
                f.seek cursor
                if f.mtime > last_checked
                  last_checked = f.mtime
                  contents = f.read
                  cursor += contents.length
                  print contents
                  STDOUT.flush
                end
                sleep 0.1
              end
            end
          end
          sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
          tail_thread
        end
    end
  end
end