thin 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ == 1.5.0 Knife
2
+ * Fix compilation under Ubuntu 12.04 with -Werror=format-security option.
3
+ * Raise an error when no PID file.
4
+ * Prevent duplicate response headers.
5
+ * Make proper response on exception [MasterLambaster].
6
+ * Automatically close idling pipeline connections on server stop [MasterLambaster].
7
+
1
8
  == 1.4.1 Chromeo Fix
2
9
  * Fix error when sending USR1 signal and no log file is supplied.
3
10
 
data/README.md CHANGED
@@ -42,28 +42,10 @@ cd to/your/app
42
42
  thin start
43
43
  ```
44
44
 
45
- But Thin is also usable with a Rack config file.
46
- You need to setup a config.ru file and pass it to the thin script:
47
-
48
- ```ruby
49
- #cat config.ru
50
- app = proc do |env|
51
- [
52
- 200,
53
- {
54
- 'Content-Type' => 'text/html',
55
- 'Content-Length' => '2'
56
- },
57
- ['hi']
58
- ]
59
- end
60
-
61
- run app
62
- ```
63
-
64
- ` thin start -R config.ru `
45
+ When using with Rails and Bundler, make sure to add `gem 'thin'`
46
+ to your Gemfile.
65
47
 
66
- See example directory for more samples and run 'thin -h' for usage.
48
+ See example directory for samples and run 'thin -h' for usage.
67
49
 
68
50
  License
69
51
  =======
@@ -72,9 +54,8 @@ Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
72
54
  Credits
73
55
  =======
74
56
  The parser was stolen from Mongrel http://mongrel.rubyforge.org by Zed Shaw.
75
- Mongrel Web Server (Mongrel) is copyrighted free software by Zed A. Shaw
76
- <zedshaw at zedshaw dot com> You can redistribute it and/or modify it under
77
- either the terms of the GPL.
57
+ Mongrel is copyright 2007 Zed A. Shaw and contributors. It is licensed under
58
+ the Ruby license and the GPL2.
78
59
 
79
60
  Thin is copyright Marc-Andre Cournoyer <macournoyer@gmail.com>
80
61
 
data/Rakefile CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'rake'
2
- require 'rake/clean'
3
2
  load 'thin.gemspec'
4
3
 
5
4
  # Load tasks in tasks/
@@ -17,5 +16,9 @@ task :push => :build do
17
16
  sh "gem push thin-*.gem"
18
17
  end
19
18
 
19
+ task :install => :build do
20
+ sh "gem install thin-*.gem"
21
+ end
22
+
20
23
  desc "Release version #{Thin::VERSION::STRING}"
21
24
  task :release => [:tag, :push]
@@ -47,7 +47,7 @@ static VALUE global_path_info;
47
47
  #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length."
48
48
 
49
49
  /** Validates the max length of given input and throws an HttpParserError exception if over. */
50
- #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); }
50
+ #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, "%s", MAX_##N##_LENGTH_ERR); }
51
51
 
52
52
  /** Defines global strings in the init method. */
53
53
  #define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
@@ -329,7 +329,7 @@ VALUE Thin_HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE star
329
329
  dlen = RSTRING_LEN(data);
330
330
 
331
331
  if(from >= dlen) {
332
- rb_raise(eHttpParserError, "Requested start is after data buffer end.");
332
+ rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
333
333
  } else {
334
334
  http->data = (void *)req_hash;
335
335
  thin_http_parser_execute(http, dptr, dlen, from);
@@ -337,7 +337,7 @@ VALUE Thin_HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE star
337
337
  VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
338
338
 
339
339
  if(thin_http_parser_has_error(http)) {
340
- rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
340
+ rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
341
341
  } else {
342
342
  return INT2FIX(http_parser_nread(http));
343
343
  }
@@ -71,6 +71,8 @@ module Thin
71
71
 
72
72
  # Do not accept anymore connection
73
73
  disconnect
74
+ # Close idle persistent connections
75
+ @connections.each { |connection| connection.close_connection if connection.idle? }
74
76
  stop! if @connections.empty?
75
77
  end
76
78
 
@@ -34,12 +34,13 @@ module Thin
34
34
 
35
35
  # Called when data is received from the client.
36
36
  def receive_data(data)
37
+ @idle = false
37
38
  trace { data }
38
39
  process if @request.parse(data)
39
40
  rescue InvalidRequest => e
40
41
  log "!! Invalid request"
41
42
  log_error e
42
- close_connection
43
+ post_process Response::BAD_REQUEST
43
44
  end
44
45
 
45
46
  # Called when all data was received and the request
@@ -82,8 +83,8 @@ module Thin
82
83
  response
83
84
  rescue Exception
84
85
  handle_error
85
- terminate_request
86
- nil # Signal to post_process that the request could not be processed
86
+ # Pass through error response
87
+ can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
87
88
  end
88
89
 
89
90
  def post_process(result)
@@ -108,6 +109,8 @@ module Thin
108
109
 
109
110
  rescue Exception
110
111
  handle_error
112
+ # Close connection since we can't handle response gracefully
113
+ close_connection
111
114
  ensure
112
115
  # If the body is being deferred, then terminate afterward.
113
116
  if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
@@ -123,7 +126,6 @@ module Thin
123
126
  def handle_error
124
127
  log "!! Unexpected error while processing request: #{$!.message}"
125
128
  log_error
126
- close_connection rescue nil
127
129
  end
128
130
 
129
131
  def close_request_response
@@ -142,6 +144,8 @@ module Thin
142
144
  close_request_response
143
145
  else
144
146
  close_request_response
147
+ # Connection become idle but it's still open
148
+ @idle = true
145
149
  # Prepare the connection for another request if the client
146
150
  # supports HTTP pipelining (persistent connection).
147
151
  post_init
@@ -172,6 +176,12 @@ module Thin
172
176
  @can_persist && @response.persistent?
173
177
  end
174
178
 
179
+ # Return +true+ if the connection is open but is not
180
+ # processing any user requests
181
+ def idle?
182
+ @idle
183
+ end
184
+
175
185
  # +true+ if <tt>app.call</tt> will be called inside a thread.
176
186
  # You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
177
187
  # or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
@@ -16,6 +16,7 @@ end
16
16
  module Thin
17
17
  # Raised when the pid file already exist starting as a daemon.
18
18
  class PidFileExist < RuntimeError; end
19
+ class PidFileNotFound < RuntimeError; end
19
20
 
20
21
  # Module included in classes that can be turned into a daemon.
21
22
  # Handle stuff like:
@@ -72,6 +73,9 @@ module Thin
72
73
  target_gid = Etc.getgrnam(group).gid
73
74
 
74
75
  if uid != target_uid || gid != target_gid
76
+ # Change PID file ownership
77
+ File.chown(target_uid, target_gid, @pid_file)
78
+
75
79
  # Change process ownership
76
80
  Process.initgroups(user, target_gid)
77
81
  Process::GID.change_privilege(target_gid)
@@ -124,7 +128,7 @@ module Thin
124
128
  sleep 0.1 while Process.running?(pid)
125
129
  end
126
130
  else
127
- Logging.log "Can't stop process, no PID found in #{pid_file}"
131
+ raise PidFileNotFound, "Can't stop process, no PID found in #{pid_file}"
128
132
  end
129
133
  rescue Timeout::Error
130
134
  Logging.log "Timeout!"
@@ -3,7 +3,7 @@ module Thin
3
3
  # and allow duplicated entries on some names.
4
4
  class Headers
5
5
  HEADER_FORMAT = "%s: %s\r\n".freeze
6
- ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate).freeze
6
+ ALLOWED_DUPLICATES = %w(set-cookie set-cookie2 warning www-authenticate).freeze
7
7
 
8
8
  def initialize
9
9
  @sent = {}
@@ -14,8 +14,9 @@ module Thin
14
14
  # Ignore if already sent and no duplicates are allowed
15
15
  # for this +key+.
16
16
  def []=(key, value)
17
- if !@sent.has_key?(key) || ALLOWED_DUPLICATES.include?(key)
18
- @sent[key] = true
17
+ downcase_key = key.downcase
18
+ if !@sent.has_key?(downcase_key) || ALLOWED_DUPLICATES.include?(downcase_key)
19
+ @sent[downcase_key] = true
19
20
  value = case value
20
21
  when Time
21
22
  value.httpdate
@@ -29,11 +30,11 @@ module Thin
29
30
  end
30
31
 
31
32
  def has_key?(key)
32
- @sent[key]
33
+ @sent[key.downcase]
33
34
  end
34
35
 
35
36
  def to_s
36
37
  @out.join
37
38
  end
38
39
  end
39
- end
40
+ end
@@ -74,7 +74,7 @@ module Thin
74
74
  # Returns +true+ if the parsing is complete.
75
75
  def parse(data)
76
76
  if @parser.finished? # Header finished, can only be some more body
77
- body << data
77
+ @body << data
78
78
  else # Parse more header using the super parser
79
79
  @data << data
80
80
  raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
@@ -9,6 +9,11 @@ module Thin
9
9
 
10
10
  PERSISTENT_STATUSES = [100, 101].freeze
11
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
+
12
17
  # Status code
13
18
  attr_accessor :status
14
19
 
@@ -130,6 +130,7 @@ module Thin
130
130
  opts.separator "Common options:"
131
131
 
132
132
  opts.on_tail("-r", "--require FILE", "require the library") { |file| @options[:require] << file }
133
+ opts.on_tail("-q", "--quiet", "Silence all logging") { @options[:quiet] = true }
133
134
  opts.on_tail("-D", "--debug", "Set debugging on") { @options[:debug] = true }
134
135
  opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { @options[:trace] = true }
135
136
  opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
@@ -173,6 +174,7 @@ module Thin
173
174
  @options[:require].each { |r| ruby_require r }
174
175
  Logging.debug = @options[:debug]
175
176
  Logging.trace = @options[:trace]
177
+ Logging.silent = @options[:quiet]
176
178
 
177
179
  controller = case
178
180
  when cluster? then Controllers::Cluster.new(@options)
@@ -55,7 +55,7 @@ module Thin
55
55
  DEFAULT_HOST = '0.0.0.0'
56
56
  DEFAULT_PORT = 3000
57
57
  DEFAULT_MAXIMUM_CONNECTIONS = 1024
58
- DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 512
58
+ DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 100
59
59
 
60
60
  # Application (Rack adapter) called with the request that produces the response.
61
61
  attr_accessor :app
@@ -5,12 +5,12 @@ module Thin
5
5
 
6
6
  module VERSION #:nodoc:
7
7
  MAJOR = 1
8
- MINOR = 4
9
- TINY = 1
8
+ MINOR = 5
9
+ TINY = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, TINY].join('.')
12
12
 
13
- CODENAME = "Chromeo".freeze
13
+ CODENAME = "Knife".freeze
14
14
 
15
15
  RACK = [1, 0].freeze # Rack protocol version
16
16
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: thin
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.4.1
5
+ version: 1.5.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Marc-Andre Cournoyer
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-07-03 00:00:00 -04:00
13
+ date: 2012-09-22 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency