gross 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +410 -0
  3. data/README.md +102 -0
  4. data/Rakefile +25 -0
  5. data/bin/thin +6 -0
  6. data/example/adapter.rb +32 -0
  7. data/example/async_app.ru +126 -0
  8. data/example/async_chat.ru +247 -0
  9. data/example/async_tailer.ru +100 -0
  10. data/example/config.ru +22 -0
  11. data/example/monit_sockets +20 -0
  12. data/example/monit_unixsock +20 -0
  13. data/example/myapp.rb +1 -0
  14. data/example/ramaze.ru +12 -0
  15. data/example/thin.god +80 -0
  16. data/example/thin_solaris_smf.erb +36 -0
  17. data/example/thin_solaris_smf.readme.txt +150 -0
  18. data/example/vlad.rake +72 -0
  19. data/ext/thin_parser/common.rl +59 -0
  20. data/ext/thin_parser/ext_help.h +14 -0
  21. data/ext/thin_parser/extconf.rb +6 -0
  22. data/ext/thin_parser/parser.c +1447 -0
  23. data/ext/thin_parser/parser.h +49 -0
  24. data/ext/thin_parser/parser.rl +152 -0
  25. data/ext/thin_parser/thin.c +435 -0
  26. data/lib/rack/adapter/loader.rb +75 -0
  27. data/lib/rack/adapter/rails.rb +178 -0
  28. data/lib/thin.rb +45 -0
  29. data/lib/thin/backends/base.rb +167 -0
  30. data/lib/thin/backends/swiftiply_client.rb +56 -0
  31. data/lib/thin/backends/tcp_server.rb +34 -0
  32. data/lib/thin/backends/unix_server.rb +56 -0
  33. data/lib/thin/command.rb +53 -0
  34. data/lib/thin/connection.rb +215 -0
  35. data/lib/thin/controllers/cluster.rb +178 -0
  36. data/lib/thin/controllers/controller.rb +189 -0
  37. data/lib/thin/controllers/service.rb +76 -0
  38. data/lib/thin/controllers/service.sh.erb +39 -0
  39. data/lib/thin/daemonizing.rb +180 -0
  40. data/lib/thin/headers.rb +40 -0
  41. data/lib/thin/logging.rb +174 -0
  42. data/lib/thin/request.rb +162 -0
  43. data/lib/thin/response.rb +117 -0
  44. data/lib/thin/runner.rb +238 -0
  45. data/lib/thin/server.rb +290 -0
  46. data/lib/thin/stats.html.erb +216 -0
  47. data/lib/thin/stats.rb +52 -0
  48. data/lib/thin/statuses.rb +44 -0
  49. data/lib/thin/version.rb +32 -0
  50. metadata +156 -0
@@ -0,0 +1,40 @@
1
+ module Thin
2
+ # Store HTTP header name-value pairs direcly to a string
3
+ # and allow duplicated entries on some names.
4
+ class Headers
5
+ HEADER_FORMAT = "%s: %s\r\n".freeze
6
+ ALLOWED_DUPLICATES = %w(set-cookie set-cookie2 warning www-authenticate).freeze
7
+
8
+ def initialize
9
+ @sent = {}
10
+ @out = []
11
+ end
12
+
13
+ # Add <tt>key: value</tt> pair to the headers.
14
+ # Ignore if already sent and no duplicates are allowed
15
+ # for this +key+.
16
+ def []=(key, value)
17
+ downcase_key = key.downcase
18
+ if !@sent.has_key?(downcase_key) || ALLOWED_DUPLICATES.include?(downcase_key)
19
+ @sent[downcase_key] = true
20
+ value = case value
21
+ when Time
22
+ value.httpdate
23
+ when NilClass
24
+ return
25
+ else
26
+ value.to_s
27
+ end
28
+ @out << HEADER_FORMAT % [key, value]
29
+ end
30
+ end
31
+
32
+ def has_key?(key)
33
+ @sent[key.downcase]
34
+ end
35
+
36
+ def to_s
37
+ @out.join
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,174 @@
1
+ require 'logger'
2
+
3
+ module Thin
4
+ # To be included in classes to allow some basic logging
5
+ # that can be silenced (<tt>Logging.silent=</tt>) or made
6
+ # more verbose.
7
+ # <tt>Logging.trace=</tt>: log all raw request and response and
8
+ # messages logged with +trace+.
9
+ # <tt>Logging.silent=</tt>: silence all log all log messages
10
+ # altogether.
11
+ module Logging
12
+ # Simple formatter which only displays the message.
13
+ # Taken from ActiveSupport
14
+ class SimpleFormatter < Logger::Formatter
15
+ def call(severity, timestamp, progname, msg)
16
+ "#{String === msg ? msg : msg.inspect}\n"
17
+ end
18
+ end
19
+
20
+ @trace_logger = nil
21
+
22
+ class << self
23
+ attr_reader :logger
24
+ attr_reader :trace_logger
25
+
26
+ def trace=(enabled)
27
+ if enabled
28
+ @trace_logger ||= Logger.new(STDOUT)
29
+ else
30
+ @trace_logger = nil
31
+ end
32
+ end
33
+
34
+ def trace?
35
+ !@trace_logger.nil?
36
+ end
37
+
38
+ def silent=(shh)
39
+ if shh
40
+ @logger = nil
41
+ else
42
+ @logger ||= Logger.new(STDOUT)
43
+ end
44
+ end
45
+
46
+ def silent?
47
+ !@logger.nil?
48
+ end
49
+
50
+ def level
51
+ @logger ? @logger.level : nil # or 'silent'
52
+ end
53
+
54
+ def level=(value)
55
+ # If logging has been silenced, then re-enable logging
56
+ @logger = Logger.new(STDOUT) if @logger.nil?
57
+ @logger.level = value
58
+ end
59
+
60
+ # Allow user to specify a custom logger to use.
61
+ # This object must respond to:
62
+ # +level+, +level=+ and +debug+, +info+, +warn+, +error+, +fatal+
63
+ def logger=(custom_logger)
64
+ [ :level ,
65
+ :level= ,
66
+ :debug ,
67
+ :info ,
68
+ :warn ,
69
+ :error ,
70
+ :fatal ,
71
+ :unknown ,
72
+ ].each do |method|
73
+ if not custom_logger.respond_to?(method)
74
+ raise ArgumentError, "logger must respond to #{method}"
75
+ end
76
+ end
77
+
78
+ @logger = custom_logger
79
+ end
80
+
81
+ def trace_logger=(custom_tracer)
82
+ [ :level ,
83
+ :level= ,
84
+ :debug ,
85
+ :info ,
86
+ :warn ,
87
+ :error ,
88
+ :fatal ,
89
+ :unknown ,
90
+ ].each do |method|
91
+ if not custom_tracer.respond_to?(method)
92
+ raise ArgumentError, "trace logger must respond to #{method}"
93
+ end
94
+ end
95
+
96
+ @trace_logger = custom_tracer
97
+ end
98
+
99
+ def log_msg(msg, level=Logger::INFO)
100
+ return unless @logger
101
+ @logger.add(level, msg)
102
+ end
103
+
104
+ def trace_msg(msg)
105
+ return unless @trace_logger
106
+ @trace_logger.info(msg)
107
+ end
108
+
109
+ # Provided for backwards compatibility.
110
+ # Callers should be using the +level+ (on the +Logging+ module
111
+ # or on the instance) to figure out what the log level is.
112
+ def debug?
113
+ self.level == Logger::DEBUG
114
+ end
115
+ def debug=(val)
116
+ self.level = (val ? Logger::DEBUG : Logger::INFO)
117
+ end
118
+
119
+ end # module methods
120
+
121
+ # Default logger to stdout.
122
+ self.logger = Logger.new(STDOUT)
123
+ self.logger.level = Logger::INFO
124
+ self.logger.formatter = Logging::SimpleFormatter.new
125
+
126
+ def silent
127
+ Logging.silent?
128
+ end
129
+
130
+ def silent=(value)
131
+ Logging.silent = value
132
+ end
133
+
134
+ # Log a message if tracing is activated
135
+ def trace(msg=nil)
136
+ Logging.trace_msg(msg) if msg
137
+ end
138
+ module_function :trace
139
+ public :trace
140
+
141
+ # Log a message at DEBUG level
142
+ def log_debug(msg=nil)
143
+ Logging.log_msg(msg || yield, Logger::DEBUG)
144
+ end
145
+ module_function :log_debug
146
+ public :log_debug
147
+
148
+ # Log a message at INFO level
149
+ def log_info(msg)
150
+ Logging.log_msg(msg || yield, Logger::INFO)
151
+ end
152
+ module_function :log_info
153
+ public :log_info
154
+
155
+ # Log a message at ERROR level (and maybe a backtrace)
156
+ def log_error(msg, e=nil)
157
+ log_msg = msg
158
+ if e
159
+ log_msg += ": #{e}\n\t" + e.backtrace.join("\n\t") + "\n"
160
+ end
161
+ Logging.log_msg(log_msg, Logger::ERROR)
162
+ end
163
+ module_function :log_error
164
+ public :log_error
165
+
166
+ # For backwards compatibility
167
+ def log msg
168
+ STDERR.puts('#log has been deprecated, please use the ' \
169
+ 'log_level function instead (e.g. - log_info).')
170
+ log_info(msg)
171
+ end
172
+
173
+ end
174
+ end
@@ -0,0 +1,162 @@
1
+ require 'tempfile'
2
+
3
+ module Thin
4
+ # Raised when an incoming request is not valid
5
+ # and the server can not process it.
6
+ class InvalidRequest < IOError; end
7
+
8
+ # A request sent by the client to the server.
9
+ class Request
10
+ # Maximum request body size before it is moved out of memory
11
+ # and into a tempfile for reading.
12
+ MAX_BODY = 1024 * (80 + 32)
13
+ BODY_TMPFILE = 'thin-body'.freeze
14
+ MAX_HEADER = 1024 * (80 + 32)
15
+
16
+ INITIAL_BODY = String.new
17
+ # Force external_encoding of request's body to ASCII_8BIT
18
+ INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!) && defined?(Encoding::ASCII_8BIT)
19
+
20
+ # Freeze some HTTP header names & values
21
+ SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
22
+ SERVER_NAME = 'SERVER_NAME'.freeze
23
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
24
+ LOCALHOST = 'localhost'.freeze
25
+ HTTP_VERSION = 'HTTP_VERSION'.freeze
26
+ HTTP_1_0 = 'HTTP/1.0'.freeze
27
+ REMOTE_ADDR = 'REMOTE_ADDR'.freeze
28
+ CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
29
+ CONNECTION = 'HTTP_CONNECTION'.freeze
30
+ KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
31
+ CLOSE_REGEXP = /\bclose\b/i.freeze
32
+ HEAD = 'HEAD'.freeze
33
+
34
+ # Freeze some Rack header names
35
+ RACK_INPUT = 'rack.input'.freeze
36
+ RACK_VERSION = 'rack.version'.freeze
37
+ RACK_ERRORS = 'rack.errors'.freeze
38
+ RACK_MULTITHREAD = 'rack.multithread'.freeze
39
+ RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
40
+ RACK_RUN_ONCE = 'rack.run_once'.freeze
41
+ ASYNC_CALLBACK = 'async.callback'.freeze
42
+ ASYNC_CLOSE = 'async.close'.freeze
43
+
44
+ # CGI-like request environment variables
45
+ attr_reader :env
46
+
47
+ # Unparsed data of the request
48
+ attr_reader :data
49
+
50
+ # Request body
51
+ attr_reader :body
52
+
53
+ def initialize
54
+ @parser = Thin::HttpParser.new
55
+ @data = String.new
56
+ @nparsed = 0
57
+ @body = StringIO.new(INITIAL_BODY.dup)
58
+ @env = {
59
+ SERVER_SOFTWARE => SERVER,
60
+ SERVER_NAME => LOCALHOST,
61
+
62
+ # Rack stuff
63
+ RACK_INPUT => @body,
64
+
65
+ RACK_VERSION => VERSION::RACK,
66
+ RACK_ERRORS => STDERR,
67
+
68
+ RACK_MULTITHREAD => false,
69
+ RACK_MULTIPROCESS => false,
70
+ RACK_RUN_ONCE => false
71
+ }
72
+ end
73
+
74
+ # Parse a chunk of data into the request environment
75
+ # Raises an +InvalidRequest+ if invalid.
76
+ # Returns +true+ if the parsing is complete.
77
+ def parse(data)
78
+ if @parser.finished? # Header finished, can only be some more body
79
+ @body << data
80
+ else # Parse more header using the super parser
81
+ @data << data
82
+ raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
83
+
84
+ @nparsed = @parser.execute(@env, @data, @nparsed)
85
+
86
+ # Transfer to a tempfile if body is very big
87
+ move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
88
+ end
89
+
90
+
91
+ if finished? # Check if header and body are complete
92
+ @data = nil
93
+ @body.rewind
94
+ true # Request is fully parsed
95
+ else
96
+ false # Not finished, need more data
97
+ end
98
+ end
99
+
100
+ # +true+ if headers and body are finished parsing
101
+ def finished?
102
+ @parser.finished? && @body.size >= content_length
103
+ end
104
+
105
+ # Expected size of the body
106
+ def content_length
107
+ @env[CONTENT_LENGTH].to_i
108
+ end
109
+
110
+ # Returns +true+ if the client expects the connection to be persistent.
111
+ def persistent?
112
+ # Clients and servers SHOULD NOT assume that a persistent connection
113
+ # is maintained for HTTP versions less than 1.1 unless it is explicitly
114
+ # signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
115
+ if @env[HTTP_VERSION] == HTTP_1_0
116
+ @env[CONNECTION] =~ KEEP_ALIVE_REGEXP
117
+
118
+ # HTTP/1.1 client intends to maintain a persistent connection unless
119
+ # a Connection header including the connection-token "close" was sent
120
+ # in the request
121
+ else
122
+ @env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
123
+ end
124
+ end
125
+
126
+ def remote_address=(address)
127
+ @env[REMOTE_ADDR] = address
128
+ end
129
+
130
+ def threaded=(value)
131
+ @env[RACK_MULTITHREAD] = value
132
+ end
133
+
134
+ def async_callback=(callback)
135
+ @env[ASYNC_CALLBACK] = callback
136
+ @env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
137
+ end
138
+
139
+ def async_close
140
+ @async_close ||= @env[ASYNC_CLOSE]
141
+ end
142
+
143
+ def head?
144
+ @env[REQUEST_METHOD] == HEAD
145
+ end
146
+
147
+ # Close any resource used by the request
148
+ def close
149
+ @body.close! if @body.class == Tempfile
150
+ end
151
+
152
+ private
153
+ def move_body_to_tempfile
154
+ current_body = @body
155
+ current_body.rewind
156
+ @body = Tempfile.new(BODY_TMPFILE)
157
+ @body.binmode
158
+ @body << current_body.read
159
+ @env[RACK_INPUT] = @body
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,117 @@
1
+ module Thin
2
+ # A response sent to the client.
3
+ class Response
4
+ CONNECTION = 'Connection'.freeze
5
+ CLOSE = 'close'.freeze
6
+ KEEP_ALIVE = 'keep-alive'.freeze
7
+ SERVER = 'Server'.freeze
8
+ CONTENT_LENGTH = 'Content-Length'.freeze
9
+
10
+ PERSISTENT_STATUSES = [100, 101].freeze
11
+
12
+ #Error Responses
13
+ ERROR = [500, {'Content-Type' => 'text/plain'}, ['Internal server error']].freeze
14
+ PERSISTENT_ERROR = [500, {'Content-Type' => 'text/plain', 'Connection' => 'keep-alive', 'Content-Length' => "21"}, ['Internal server error']].freeze
15
+ BAD_REQUEST = [400, {'Content-Type' => 'text/plain'}, ['Bad Request']].freeze
16
+
17
+ # Status code
18
+ attr_accessor :status
19
+
20
+ # Response body, must respond to +each+.
21
+ attr_accessor :body
22
+
23
+ # Headers key-value hash
24
+ attr_reader :headers
25
+
26
+ def initialize
27
+ @headers = Headers.new
28
+ @status = 200
29
+ @persistent = false
30
+ @skip_body = false
31
+ end
32
+
33
+ # String representation of the headers
34
+ # to be sent in the response.
35
+ def headers_output
36
+ # Set default headers
37
+ @headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE unless @headers.has_key?(CONNECTION)
38
+ @headers[SERVER] = Thin::NAME unless @headers.has_key?(SERVER)
39
+
40
+ @headers.to_s
41
+ end
42
+
43
+ # Top header of the response,
44
+ # containing the status code and response headers.
45
+ def head
46
+ "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
47
+ end
48
+
49
+ if Thin.ruby_18?
50
+
51
+ # Ruby 1.8 implementation.
52
+ # Respects Rack specs.
53
+ #
54
+ # See http://rack.rubyforge.org/doc/files/SPEC.html
55
+ def headers=(key_value_pairs)
56
+ key_value_pairs.each do |k, vs|
57
+ vs.each { |v| @headers[k] = v.chomp } if vs
58
+ end if key_value_pairs
59
+ end
60
+
61
+ else
62
+
63
+ # Ruby 1.9 doesn't have a String#each anymore.
64
+ # Rack spec doesn't take care of that yet, for now we just use
65
+ # +each+ but fallback to +each_line+ on strings.
66
+ # I wish we could remove that condition.
67
+ # To be reviewed when a new Rack spec comes out.
68
+ def headers=(key_value_pairs)
69
+ key_value_pairs.each do |k, vs|
70
+ next unless vs
71
+ if vs.is_a?(String)
72
+ vs.each_line { |v| @headers[k] = v.chomp }
73
+ else
74
+ vs.each { |v| @headers[k] = v.chomp }
75
+ end
76
+ end if key_value_pairs
77
+ end
78
+
79
+ end
80
+
81
+ # Close any resource used by the response
82
+ def close
83
+ @body.close if @body.respond_to?(:close)
84
+ end
85
+
86
+ # Yields each chunk of the response.
87
+ # To control the size of each chunk
88
+ # define your own +each+ method on +body+.
89
+ def each
90
+ yield head
91
+
92
+ unless @skip_body
93
+ if @body.is_a?(String)
94
+ yield @body
95
+ else
96
+ @body.each { |chunk| yield chunk }
97
+ end
98
+ end
99
+ end
100
+
101
+ # Tell the client the connection should stay open
102
+ def persistent!
103
+ @persistent = true
104
+ end
105
+
106
+ # Persistent connection must be requested as keep-alive
107
+ # from the server and have a Content-Length, or the response
108
+ # status must require that the connection remain open.
109
+ def persistent?
110
+ (@persistent && @headers.has_key?(CONTENT_LENGTH)) || PERSISTENT_STATUSES.include?(@status)
111
+ end
112
+
113
+ def skip_body!
114
+ @skip_body = true
115
+ end
116
+ end
117
+ end