thin 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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