thin 0.5.4-x86-mswin32-60 → 0.6.0-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of thin might be problematic. Click here for more details.

@@ -29,34 +29,50 @@
29
29
 
30
30
  action start_value { MARK(mark, fpc); }
31
31
  action write_value {
32
- parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
32
+ if (parser->http_field != NULL) {
33
+ parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
34
+ }
33
35
  }
34
36
  action request_method {
35
- parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
37
+ if (parser->request_method != NULL) {
38
+ parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
39
+ }
36
40
  }
37
- action request_uri {
38
- parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
41
+ action request_uri {
42
+ if (parser->request_uri != NULL) {
43
+ parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
44
+ }
39
45
  }
40
46
  action fragment {
41
- parser->fragment(parser->data, PTR_TO(mark), LEN(mark, fpc));
47
+ if (parser->fragment != NULL) {
48
+ parser->fragment(parser->data, PTR_TO(mark), LEN(mark, fpc));
49
+ }
42
50
  }
43
51
 
44
52
  action start_query {MARK(query_start, fpc); }
45
53
  action query_string {
46
- parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
54
+ if (parser->query_string != NULL) {
55
+ parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
56
+ }
47
57
  }
48
58
 
49
59
  action http_version {
50
- parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
60
+ if (parser->http_version != NULL) {
61
+ parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
62
+ }
51
63
  }
52
64
 
53
65
  action request_path {
54
- parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
66
+ if (parser->request_path != NULL) {
67
+ parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
68
+ }
55
69
  }
56
70
 
57
71
  action done {
58
72
  parser->body_start = fpc - buffer + 1;
59
- parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
73
+ if (parser->header_done != NULL) {
74
+ parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
75
+ }
60
76
  fbreak;
61
77
  }
62
78
 
@@ -22,6 +22,7 @@ module Thin
22
22
  autoload :Request, 'thin/request'
23
23
  autoload :Response, 'thin/response'
24
24
  autoload :Server, 'thin/server'
25
+ autoload :Stats, 'thin/stats'
25
26
  end
26
27
 
27
28
  require 'rack'
@@ -20,36 +20,42 @@ module Thin
20
20
  def initialize(options)
21
21
  @options = options.merge(:daemonize => true)
22
22
  @size = @options.delete(:servers)
23
- @script = 'thin'
23
+ @script = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'thin')
24
+
25
+ if socket
26
+ @options.delete(:address)
27
+ @options.delete(:port)
28
+ end
24
29
  end
25
30
 
26
31
  def first_port; @options[:port] end
27
- def address; @options[:address] end
32
+ def address; @options[:address] end
33
+ def socket; @options[:socket] end
28
34
  def pid_file; File.expand_path File.join(@options[:chdir], @options[:pid]) end
29
35
  def log_file; File.expand_path File.join(@options[:chdir], @options[:log]) end
30
36
 
31
37
  # Start the servers
32
38
  def start
33
- with_each_server { |port| start_on_port port }
39
+ with_each_server { |port| start_server port }
34
40
  end
35
41
 
36
- # Start the server on a single port
37
- def start_on_port(port)
38
- log "Starting #{address}:#{port} ... "
42
+ # Start a single server
43
+ def start_server(number)
44
+ log "Starting server on #{server_id(number)} ... "
39
45
 
40
- run :start, @options, port
46
+ run :start, @options, number
41
47
  end
42
48
 
43
49
  # Stop the servers
44
50
  def stop
45
- with_each_server { |port| stop_on_port port }
51
+ with_each_server { |n| stop_server n }
46
52
  end
47
53
 
48
- # Stop the server running on +port+
49
- def stop_on_port(port)
50
- log "Stopping #{address}:#{port} ... "
54
+ # Stop a single server
55
+ def stop_server(number)
56
+ log "Stopping server on #{server_id(number)} ... "
51
57
 
52
- run :stop, @options, port
58
+ run :stop, @options, number
53
59
  end
54
60
 
55
61
  # Stop and start the servers.
@@ -59,25 +65,44 @@ module Thin
59
65
  start
60
66
  end
61
67
 
62
- def log_file_for(port)
63
- include_port_number log_file, port
68
+ def server_id(number)
69
+ if socket
70
+ socket_for(number)
71
+ else
72
+ [address, number].join(':')
73
+ end
74
+ end
75
+
76
+ def log_file_for(number)
77
+ include_server_number log_file, number
64
78
  end
65
79
 
66
- def pid_file_for(port)
67
- include_port_number pid_file, port
80
+ def pid_file_for(number)
81
+ include_server_number pid_file, number
68
82
  end
69
83
 
70
- def pid_for(port)
71
- File.read(pid_file_for(port)).chomp.to_i
84
+ def socket_for(number)
85
+ include_server_number socket, number
86
+ end
87
+
88
+ def pid_for(number)
89
+ File.read(pid_file_for(number)).chomp.to_i
72
90
  end
73
91
 
74
92
  private
75
93
  # Send the command to the +thin+ script
76
- def run(cmd, options, port)
77
- shell_cmd = shellify(cmd, options.merge(:port => port, :pid => pid_file_for(port), :log => log_file_for(port)))
94
+ def run(cmd, options, number)
95
+ cmd_options = options.dup
96
+ cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
97
+ if socket
98
+ cmd_options.merge!(:socket => socket_for(number))
99
+ else
100
+ cmd_options.merge!(:port => number)
101
+ end
102
+ shell_cmd = shellify(cmd, cmd_options)
78
103
  trace shell_cmd
79
104
  ouput = `#{shell_cmd}`.chomp
80
- log ouput unless ouput.empty?
105
+ log " " + ouput.gsub("\n", " \n") unless ouput.empty?
81
106
  end
82
107
 
83
108
  # Turn into a runnable shell command
@@ -93,14 +118,16 @@ module Thin
93
118
  end
94
119
 
95
120
  def with_each_server
96
- @size.times { |n| yield first_port + n }
121
+ @size.times do |n|
122
+ yield socket ? n : (first_port + n)
123
+ end
97
124
  end
98
125
 
99
- # Add the port numbers in the filename
126
+ # Add the server port or number in the filename
100
127
  # so each instance get its own file
101
- def include_port_number(path, port)
102
- raise ArgumentError, "filename '#{path}' must include an extension" unless path =~ /\./
103
- path.gsub(/\.(.+)$/) { ".#{port}.#{$1}" }
128
+ def include_server_number(path, number)
129
+ ext = File.extname(path)
130
+ path.gsub(/#{ext}$/, ".#{number}#{ext}")
104
131
  end
105
132
  end
106
133
  end
@@ -4,8 +4,12 @@ module Thin
4
4
  class Connection < EventMachine::Connection
5
5
  include Logging
6
6
 
7
+ # Rack application served by this connection.
7
8
  attr_accessor :app
8
9
 
10
+ # +true+ if the connection is on a UNIX domain socket.
11
+ attr_accessor :unix_socket
12
+
9
13
  def post_init
10
14
  @request = Request.new
11
15
  @response = Response.new
@@ -13,7 +17,7 @@ module Thin
13
17
 
14
18
  def receive_data(data)
15
19
  trace { data }
16
- process if @request.parse(data)
20
+ process if @request.parse(data)
17
21
  rescue InvalidRequest => e
18
22
  log "Invalid request"
19
23
  log_error e
@@ -24,8 +28,8 @@ module Thin
24
28
  env = @request.env
25
29
 
26
30
  # Add client info to the request env
27
- env[Request::REMOTE_ADDR] = env[Request::FORWARDED_FOR] || Socket.unpack_sockaddr_in(get_peername)[1]
28
-
31
+ env[Request::REMOTE_ADDR] = remote_address(env)
32
+
29
33
  # Process the request
30
34
  @response.status, @response.headers, @response.body = @app.call(env)
31
35
 
@@ -42,7 +46,20 @@ module Thin
42
46
  log_error e
43
47
  close_connection rescue nil
44
48
  ensure
49
+ @request.close rescue nil
45
50
  @response.close rescue nil
46
51
  end
52
+
53
+ protected
54
+ def remote_address(env)
55
+ if remote_addr = env[Request::FORWARDED_FOR]
56
+ remote_addr
57
+ elsif @unix_socket
58
+ # FIXME not sure about this, does it even make sense on a UNIX socket?
59
+ Socket.unpack_sockaddr_un(get_peername)
60
+ else
61
+ Socket.unpack_sockaddr_in(get_peername)[1]
62
+ end
63
+ end
47
64
  end
48
65
  end
@@ -26,7 +26,7 @@ module Thin
26
26
  end
27
27
 
28
28
  def pid
29
- File.exist?(pid_file) ? open(pid_file).read : nil
29
+ File.exist?(pid_file) ? open(pid_file).read.to_i : nil
30
30
  end
31
31
 
32
32
  # Turns the current script into a daemon process that detaches from the console.
@@ -1,37 +1,24 @@
1
1
  module Thin
2
- # Acts like a Hash, but allows duplicated keys
2
+ # Store HTTP header name-value pairs direcly to a string
3
+ # and allow duplicated entries on some names.
3
4
  class Headers
4
5
  HEADER_FORMAT = "%s: %s\r\n".freeze
5
6
  ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate).freeze
6
7
 
7
8
  def initialize
8
9
  @sent = {}
9
- @items = []
10
+ @out = ''
10
11
  end
11
-
12
+
12
13
  def []=(key, value)
13
- if @sent.has_key?(key) && !ALLOWED_DUPLICATES.include?(key)
14
- # If we don't allow duplicate for that field
15
- # we overwrite the one that is already there
16
- @items.assoc(key)[1] = value
17
- else
14
+ if !@sent[key] || ALLOWED_DUPLICATES.include?(key)
18
15
  @sent[key] = true
19
- @items << [key, value]
20
- end
21
- end
22
-
23
- def [](key)
24
- if item = @items.assoc(key)
25
- item[1]
16
+ @out << HEADER_FORMAT % [key, value]
26
17
  end
27
18
  end
28
19
 
29
- def size
30
- @items.size
31
- end
32
-
33
20
  def to_s
34
- @items.inject('') { |out, (name, value)| out << HEADER_FORMAT % [name, value] }
21
+ @out
35
22
  end
36
23
  end
37
24
  end
@@ -13,7 +13,7 @@ module Thin
13
13
 
14
14
  # Log a message to the console if tracing is activated
15
15
  def trace(msg=nil)
16
- puts msg || yield if $DEBUG && !@silent
16
+ puts msg || yield if ($DEBUG || $TRACE) && !@silent
17
17
  end
18
18
 
19
19
  def log_error(e)
@@ -1,4 +1,5 @@
1
1
  require 'thin_parser'
2
+ require 'tempfile'
2
3
 
3
4
  module Thin
4
5
  # Raised when an incoming request is not valid
@@ -7,15 +8,18 @@ module Thin
7
8
 
8
9
  # A request sent by the client to the server.
9
10
  class Request
10
- MAX_HEADER = 1024 * (80 + 32)
11
- MAX_HEADER_MSG = 'Header longer than allowed'.freeze
12
-
11
+ # Maximum request body size before it is moved out of memory
12
+ # and into a tempfile for reading.
13
+ MAX_BODY = 1024 * (80 + 32)
14
+ BODY_TMPFILE = 'thin-body'.freeze
15
+
16
+ # Freeze some HTTP header names
13
17
  SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
14
18
  REMOTE_ADDR = 'REMOTE_ADDR'.freeze
15
19
  FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
16
-
17
20
  CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
18
-
21
+
22
+ # Freeze some Rack header names
19
23
  RACK_INPUT = 'rack.input'.freeze
20
24
  RACK_VERSION = 'rack.version'.freeze
21
25
  RACK_ERRORS = 'rack.errors'.freeze
@@ -23,7 +27,14 @@ module Thin
23
27
  RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
24
28
  RACK_RUN_ONCE = 'rack.run_once'.freeze
25
29
 
26
- attr_reader :env, :data, :body
30
+ # CGI-like request environment variables
31
+ attr_reader :env
32
+
33
+ # Unparsed data of the request
34
+ attr_reader :data
35
+
36
+ # Request body
37
+ attr_reader :body
27
38
 
28
39
  def initialize
29
40
  @parser = HttpParser.new
@@ -45,28 +56,46 @@ module Thin
45
56
  }
46
57
  end
47
58
 
59
+ # Parse a chunk of data into the request environment
60
+ # Raises a +InvalidRequest+ if invalid.
61
+ # Returns +true+ if the parsing is complete.
48
62
  def parse(data)
49
63
  @data << data
50
-
51
- if @parser.finished? # Header finished, can only be some more body
64
+
65
+ if @parser.finished? # Header finished, can only be some more body
52
66
  body << data
53
- elsif @data.size > MAX_HEADER # Oho! very big header, must be a mean person
54
- raise InvalidRequest, MAX_HEADER_MSG
55
- else # Parse more header using the super parser
56
- @nparsed = @parser.execute(@env, @data, @nparsed)
57
- end
58
-
59
- # Check if header and body are complete
60
- if @parser.finished? && body.size >= content_length
61
- body.rewind
62
- return true # Request is fully parsed
63
- end
64
-
65
- false # Not finished, need more data
67
+ else # Parse more header using the super parser
68
+ @nparsed = @parser.execute(@env, @data, @nparsed)
69
+ # Transfert to a tempfile if body is very big
70
+ move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
71
+ end
72
+
73
+ # Check if header and body are complete
74
+ if @parser.finished? && @body.size >= content_length
75
+ @body.rewind
76
+ return true # Request is fully parsed
77
+ end
78
+
79
+ false # Not finished, need more data
66
80
  end
67
81
 
82
+ # Expected size of the body
68
83
  def content_length
69
84
  @env[CONTENT_LENGTH].to_i
70
85
  end
86
+
87
+ def close
88
+ @body.close if @body === Tempfile
89
+ end
90
+
91
+
92
+ private
93
+ def move_body_to_tempfile
94
+ current_body = @body
95
+ @body = Tempfile.new(BODY_TMPFILE)
96
+ @body.binmode
97
+ @body << current_body unless current_body.size.zero?
98
+ @env[RACK_INPUT] = @body
99
+ end
71
100
  end
72
101
  end
@@ -36,7 +36,7 @@ module Thin
36
36
 
37
37
  def headers=(key_value_pairs)
38
38
  key_value_pairs.each do |k, vs|
39
- vs.each { |v| @headers[k] = v.chomp }
39
+ vs.each_line { |v| @headers[k] = v.chomp }
40
40
  end
41
41
  end
42
42
 
@@ -45,7 +45,9 @@ module Thin
45
45
  @body.close if @body.respond_to?(:close)
46
46
  end
47
47
 
48
- # Yields each chunk of the response
48
+ # Yields each chunk of the response.
49
+ # To control the size of each chunk
50
+ # define your own +each+ method on +body+.
49
51
  def each
50
52
  yield head
51
53
  @body.each do |chunk|
@@ -12,69 +12,110 @@ module Thin
12
12
  include Logging
13
13
  include Daemonizable
14
14
 
15
- # Addresse and port on which the server is listening for connections.
15
+ # Address and port on which the server is listening for connections.
16
16
  attr_accessor :port, :host
17
17
 
18
- # App called with the request that produce the response.
18
+ # UNIX domain socket on which the server is listening for connections.
19
+ attr_accessor :socket
20
+
21
+ # App called with the request that produces the response.
19
22
  attr_accessor :app
20
23
 
21
24
  # Maximum time for incoming data to arrive
22
25
  attr_accessor :timeout
23
26
 
24
- # Creates a new server binded to <tt>host:port</tt>
25
- # that will pass request to +app+.
26
- def initialize(host, port, app=nil)
27
- @host = host
28
- @port = port.to_i
27
+ # Creates a new server bound to <tt>host:port</tt>
28
+ # or to +socket+ that will pass request to +app+.
29
+ # If +host_or_socket+ contains a <tt>/</tt> it is assumed
30
+ # to be a UNIX domain socket filename.
31
+ # If a block is passed, a <tt>Rack::Builder</tt> instance
32
+ # will be passed to build the +app+.
33
+ #
34
+ # Server.new '0.0.0.0', 3000 do
35
+ # use Rack::CommonLogger
36
+ # use Rack::ShowExceptions
37
+ # map "/lobster" do
38
+ # use Rack::Lint
39
+ # run Rack::Lobster.new
40
+ # end
41
+ # end.start
42
+ #
43
+ def initialize(host_or_socket, port=3000, app=nil, &block)
44
+ if host_or_socket.include?('/')
45
+ @socket = host_or_socket
46
+ else
47
+ @host = host_or_socket
48
+ @port = port.to_i
49
+ end
29
50
  @app = app
30
51
  @timeout = 60 # sec
31
- end
32
-
33
- # Starts the handlers.
34
- def start
35
- raise ArgumentError, "app required" unless @app
36
52
 
37
- log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
38
- trace ">> Tracing ON"
53
+ @app = Rack::Builder.new(&block).to_app if block
39
54
  end
40
55
 
41
- # Start the server and listen for connections
42
- def start!
43
- start
44
- listen!
56
+ def self.start(*args, &block)
57
+ new(*args, &block).start!
45
58
  end
46
59
 
47
- # Start listening for connections
48
- def listen!
49
- trap('INT') { stop }
50
- trap('TERM') { stop! }
60
+ # Start the server and listen for connections
61
+ def start
62
+ raise ArgumentError, "app required" unless @app
51
63
 
64
+ trap('INT') { stop }
65
+ trap('TERM') { stop! }
66
+
52
67
  # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
53
68
  EventMachine.epoll
54
-
55
- EventMachine.run do
56
- begin
57
- log ">> Listening on #{@host}:#{@port}, CTRL+C to stop"
58
- EventMachine.start_server(@host, @port, Connection) do |connection|
59
- connection.comm_inactivity_timeout = @timeout
60
- connection.app = @app
61
- connection.silent = @silent
62
- end
63
- rescue StopServer
64
- EventMachine.stop_event_loop
65
- end
66
- end
69
+
70
+ log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
71
+ trace ">> Tracing ON"
72
+
73
+ EventMachine.run do
74
+ begin
75
+ start_server
76
+ rescue StopServer
77
+ stop
78
+ end
79
+ end
67
80
  end
81
+ alias :start! :start
68
82
 
69
-
83
+ # Stops the server by stopping the listening loop.
70
84
  def stop
71
85
  EventMachine.stop_event_loop
72
86
  rescue
73
87
  warn "Error stopping : #{$!}"
74
88
  end
75
89
 
90
+ # Stops the server by raising an error.
76
91
  def stop!
77
92
  raise StopServer
78
93
  end
94
+
95
+ protected
96
+ def start_server
97
+ if @socket
98
+ start_server_on_socket
99
+ else
100
+ start_server_on_host
101
+ end
102
+ end
103
+
104
+ def start_server_on_host
105
+ log ">> Listening on #{@host}:#{@port}, CTRL+C to stop"
106
+ EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
107
+ end
108
+
109
+ def start_server_on_socket
110
+ log ">> Listening on #{@socket}, CTRL+C to stop"
111
+ EventMachine.start_unix_domain_server(@socket, Connection, &method(:initialize_connection))
112
+ end
113
+
114
+ def initialize_connection(connection)
115
+ connection.comm_inactivity_timeout = @timeout
116
+ connection.app = @app
117
+ connection.silent = @silent
118
+ connection.unix_socket = !@socket.nil?
119
+ end
79
120
  end
80
121
  end