giga-smart-mod 0.0.1
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.
- checksums.yaml +7 -0
- data/giga-smart-mod.gemspec +12 -0
- data/thin-2.0.1/CHANGELOG +422 -0
- data/thin-2.0.1/README.md +88 -0
- data/thin-2.0.1/Rakefile +22 -0
- data/thin-2.0.1/bin/thin +6 -0
- data/thin-2.0.1/example/adapter.rb +32 -0
- data/thin-2.0.1/example/async_app.ru +126 -0
- data/thin-2.0.1/example/async_chat.ru +247 -0
- data/thin-2.0.1/example/async_tailer.ru +100 -0
- data/thin-2.0.1/example/config.ru +22 -0
- data/thin-2.0.1/example/monit_sockets +20 -0
- data/thin-2.0.1/example/monit_unixsock +20 -0
- data/thin-2.0.1/example/myapp.rb +1 -0
- data/thin-2.0.1/example/ramaze.ru +12 -0
- data/thin-2.0.1/example/thin.god +80 -0
- data/thin-2.0.1/example/thin_solaris_smf.erb +36 -0
- data/thin-2.0.1/example/thin_solaris_smf.readme.txt +150 -0
- data/thin-2.0.1/example/vlad.rake +72 -0
- data/thin-2.0.1/ext/thin_parser/common.rl +59 -0
- data/thin-2.0.1/ext/thin_parser/ext_help.h +14 -0
- data/thin-2.0.1/ext/thin_parser/extconf.rb +6 -0
- data/thin-2.0.1/ext/thin_parser/parser.c +1447 -0
- data/thin-2.0.1/ext/thin_parser/parser.h +49 -0
- data/thin-2.0.1/ext/thin_parser/parser.rl +152 -0
- data/thin-2.0.1/ext/thin_parser/thin.c +435 -0
- data/thin-2.0.1/lib/rack/adapter/loader.rb +82 -0
- data/thin-2.0.1/lib/rack/adapter/rails.rb +172 -0
- data/thin-2.0.1/lib/rack/handler/thin.rb +13 -0
- data/thin-2.0.1/lib/rackup/handler/thin.rb +13 -0
- data/thin-2.0.1/lib/thin/backends/base.rb +169 -0
- data/thin-2.0.1/lib/thin/backends/swiftiply_client.rb +66 -0
- data/thin-2.0.1/lib/thin/backends/tcp_server.rb +34 -0
- data/thin-2.0.1/lib/thin/backends/unix_server.rb +56 -0
- data/thin-2.0.1/lib/thin/command.rb +53 -0
- data/thin-2.0.1/lib/thin/connection.rb +219 -0
- data/thin-2.0.1/lib/thin/controllers/cluster.rb +178 -0
- data/thin-2.0.1/lib/thin/controllers/controller.rb +189 -0
- data/thin-2.0.1/lib/thin/controllers/service.rb +76 -0
- data/thin-2.0.1/lib/thin/controllers/service.sh.erb +39 -0
- data/thin-2.0.1/lib/thin/daemonizing.rb +199 -0
- data/thin-2.0.1/lib/thin/env.rb +35 -0
- data/thin-2.0.1/lib/thin/headers.rb +47 -0
- data/thin-2.0.1/lib/thin/logging.rb +174 -0
- data/thin-2.0.1/lib/thin/rackup/handler.rb +33 -0
- data/thin-2.0.1/lib/thin/request.rb +159 -0
- data/thin-2.0.1/lib/thin/response.rb +144 -0
- data/thin-2.0.1/lib/thin/runner.rb +238 -0
- data/thin-2.0.1/lib/thin/server.rb +290 -0
- data/thin-2.0.1/lib/thin/stats.html.erb +216 -0
- data/thin-2.0.1/lib/thin/stats.rb +52 -0
- data/thin-2.0.1/lib/thin/statuses.rb +48 -0
- data/thin-2.0.1/lib/thin/version.rb +19 -0
- data/thin-2.0.1/lib/thin.rb +45 -0
- metadata +94 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require 'etc'
|
|
2
|
+
require 'daemons' unless Thin.win?
|
|
3
|
+
|
|
4
|
+
module Process
|
|
5
|
+
# Returns +true+ the process identied by +pid+ is running.
|
|
6
|
+
def running?(pid)
|
|
7
|
+
Process.getpgid(pid) != -1
|
|
8
|
+
rescue Errno::EPERM
|
|
9
|
+
true
|
|
10
|
+
rescue Errno::ESRCH
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
module_function :running?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Thin
|
|
17
|
+
# Raised when the pid file already exist starting as a daemon.
|
|
18
|
+
class PidFileExist < RuntimeError; end
|
|
19
|
+
class PidFileNotFound < RuntimeError; end
|
|
20
|
+
|
|
21
|
+
# Module included in classes that can be turned into a daemon.
|
|
22
|
+
# Handle stuff like:
|
|
23
|
+
# * storing the PID in a file
|
|
24
|
+
# * redirecting output to the log file
|
|
25
|
+
# * changing process privileges
|
|
26
|
+
# * killing the process gracefully
|
|
27
|
+
module Daemonizable
|
|
28
|
+
attr_accessor :pid_file, :log_file
|
|
29
|
+
|
|
30
|
+
def self.included(base)
|
|
31
|
+
base.extend ClassMethods
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pid
|
|
35
|
+
File.exist?(pid_file) && !File.zero?(pid_file) ? open(pid_file).read.to_i : nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def kill(timeout = 60)
|
|
39
|
+
if File.exist?(@pid_file)
|
|
40
|
+
self.class.kill(@pid_file, timeout)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Turns the current script into a daemon process that detaches from the console.
|
|
45
|
+
def daemonize
|
|
46
|
+
raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if Thin.win?
|
|
47
|
+
raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
|
|
48
|
+
|
|
49
|
+
remove_stale_pid_file
|
|
50
|
+
|
|
51
|
+
pwd = Dir.pwd # Current directory is changed during daemonization, so store it
|
|
52
|
+
|
|
53
|
+
# HACK we need to create the directory before daemonization to prevent a bug under 1.9
|
|
54
|
+
# ignoring all signals when the directory is created after daemonization.
|
|
55
|
+
FileUtils.mkdir_p File.dirname(@pid_file)
|
|
56
|
+
FileUtils.mkdir_p File.dirname(@log_file)
|
|
57
|
+
|
|
58
|
+
Daemonize.daemonize(File.expand_path(@log_file), name)
|
|
59
|
+
|
|
60
|
+
Dir.chdir(pwd)
|
|
61
|
+
|
|
62
|
+
write_pid_file
|
|
63
|
+
|
|
64
|
+
at_exit do
|
|
65
|
+
log_info "Exiting!"
|
|
66
|
+
remove_pid_file
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Change privileges of the process
|
|
71
|
+
# to the specified user and group.
|
|
72
|
+
def change_privilege(user, group=user)
|
|
73
|
+
log_info "Changing process privilege to #{user}:#{group}"
|
|
74
|
+
|
|
75
|
+
uid, gid = Process.euid, Process.egid
|
|
76
|
+
target_uid = Etc.getpwnam(user).uid
|
|
77
|
+
target_gid = Etc.getgrnam(group).gid
|
|
78
|
+
|
|
79
|
+
if uid != target_uid || gid != target_gid
|
|
80
|
+
# Change PID file ownership
|
|
81
|
+
File.chown(target_uid, target_gid, @pid_file) if File.exist?(@pid_file)
|
|
82
|
+
|
|
83
|
+
# Change process ownership
|
|
84
|
+
Process.initgroups(user, target_gid)
|
|
85
|
+
Process::GID.change_privilege(target_gid)
|
|
86
|
+
Process::UID.change_privilege(target_uid)
|
|
87
|
+
|
|
88
|
+
# Correct environment variables
|
|
89
|
+
ENV.store('USER', user)
|
|
90
|
+
ENV.store('HOME', File.expand_path("~#{user}"))
|
|
91
|
+
end
|
|
92
|
+
rescue Errno::EPERM => e
|
|
93
|
+
log_info "Couldn't change user and group to #{user}:#{group}: #{e}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Register a proc to be called to restart the server.
|
|
97
|
+
def on_restart(&block)
|
|
98
|
+
@on_restart = block
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Restart the server.
|
|
102
|
+
def restart
|
|
103
|
+
if @on_restart
|
|
104
|
+
log_info 'Restarting ...'
|
|
105
|
+
stop
|
|
106
|
+
remove_pid_file
|
|
107
|
+
@on_restart.call
|
|
108
|
+
EM.next_tick { exit! }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
module ClassMethods
|
|
113
|
+
# Send a QUIT or INT (if timeout is +0+) signal the process which
|
|
114
|
+
# PID is stored in +pid_file+.
|
|
115
|
+
# If the process is still running after +timeout+, KILL signal is
|
|
116
|
+
# sent.
|
|
117
|
+
def kill(pid_file, timeout=60)
|
|
118
|
+
if timeout == 0
|
|
119
|
+
send_signal('INT', pid_file, timeout)
|
|
120
|
+
else
|
|
121
|
+
send_signal('QUIT', pid_file, timeout)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Restart the server by sending HUP signal.
|
|
126
|
+
def restart(pid_file)
|
|
127
|
+
send_signal('HUP', pid_file)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def monotonic_time
|
|
131
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Send a +signal+ to the process which PID is stored in +pid_file+.
|
|
135
|
+
def send_signal(signal, pid_file, timeout=60)
|
|
136
|
+
if pid = read_pid_file(pid_file)
|
|
137
|
+
Logging.log_info "Sending #{signal} signal to process #{pid} ... "
|
|
138
|
+
|
|
139
|
+
Process.kill(signal, pid)
|
|
140
|
+
|
|
141
|
+
# This loop seems kind of racy to me...
|
|
142
|
+
started_at = monotonic_time
|
|
143
|
+
while Process.running?(pid)
|
|
144
|
+
sleep 0.1
|
|
145
|
+
raise Timeout::Error if (monotonic_time - started_at) > timeout
|
|
146
|
+
end
|
|
147
|
+
else
|
|
148
|
+
raise PidFileNotFound, "Can't stop process, no PID found in #{pid_file}"
|
|
149
|
+
end
|
|
150
|
+
rescue Timeout::Error
|
|
151
|
+
Logging.log_info "Timeout!"
|
|
152
|
+
force_kill(pid, pid_file)
|
|
153
|
+
rescue Interrupt
|
|
154
|
+
force_kill(pid, pid_file)
|
|
155
|
+
rescue Errno::ESRCH # No such process
|
|
156
|
+
Logging.log_info "process not found!"
|
|
157
|
+
force_kill(pid, pid_file)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def force_kill(pid, pid_file)
|
|
161
|
+
Logging.log_info "Sending KILL signal to process #{pid} ... "
|
|
162
|
+
Process.kill("KILL", pid)
|
|
163
|
+
File.delete(pid_file) if File.exist?(pid_file)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def read_pid_file(file)
|
|
167
|
+
if File.file?(file) && pid = File.read(file)
|
|
168
|
+
pid.to_i
|
|
169
|
+
else
|
|
170
|
+
nil
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
protected
|
|
176
|
+
def remove_pid_file
|
|
177
|
+
File.delete(@pid_file) if @pid_file && File.exist?(@pid_file)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def write_pid_file
|
|
181
|
+
log_info "Writing PID to #{@pid_file}"
|
|
182
|
+
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
|
183
|
+
File.chmod(0644, @pid_file)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# If PID file is stale, remove it.
|
|
187
|
+
def remove_stale_pid_file
|
|
188
|
+
if File.exist?(@pid_file)
|
|
189
|
+
if pid && Process.running?(pid)
|
|
190
|
+
raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
|
|
191
|
+
"Stop the process or delete #{@pid_file}."
|
|
192
|
+
else
|
|
193
|
+
log_info "Deleting stale PID file #{@pid_file}"
|
|
194
|
+
remove_pid_file
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
module Thin
|
|
3
|
+
module Env
|
|
4
|
+
def self.with_defaults(env)
|
|
5
|
+
if ::Rack.release >= "3"
|
|
6
|
+
rack_env_class = Rack3
|
|
7
|
+
else
|
|
8
|
+
rack_env_class = Rack2
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
rack_env_class.env.merge(env)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
class Rack2
|
|
18
|
+
RACK_VERSION = [1, 0].freeze
|
|
19
|
+
|
|
20
|
+
def self.env
|
|
21
|
+
{
|
|
22
|
+
::Thin::Request::RACK_VERSION => RACK_VERSION,
|
|
23
|
+
::Thin::Request::RACK_MULTITHREAD => false,
|
|
24
|
+
::Thin::Request::RACK_MULTIPROCESS => false,
|
|
25
|
+
::Thin::Request::RACK_RUN_ONCE => false
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Rack3
|
|
31
|
+
def self.env
|
|
32
|
+
{}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Thin
|
|
2
|
+
# Raised when an header is not valid
|
|
3
|
+
# and the server can not process it.
|
|
4
|
+
class InvalidHeader < StandardError; end
|
|
5
|
+
|
|
6
|
+
# Store HTTP header name-value pairs direcly to a string
|
|
7
|
+
# and allow duplicated entries on some names.
|
|
8
|
+
class Headers
|
|
9
|
+
HEADER_FORMAT = "%s: %s\r\n".freeze
|
|
10
|
+
ALLOWED_DUPLICATES = %w(set-cookie set-cookie2 warning www-authenticate).freeze
|
|
11
|
+
CR_OR_LF = /[\r\n]/.freeze
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@sent = {}
|
|
15
|
+
@out = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Add <tt>key: value</tt> pair to the headers.
|
|
19
|
+
# Ignore if already sent and no duplicates are allowed
|
|
20
|
+
# for this +key+.
|
|
21
|
+
def []=(key, value)
|
|
22
|
+
downcase_key = key.downcase
|
|
23
|
+
if !@sent.has_key?(downcase_key) || ALLOWED_DUPLICATES.include?(downcase_key)
|
|
24
|
+
@sent[downcase_key] = true
|
|
25
|
+
value = case value
|
|
26
|
+
when Time
|
|
27
|
+
value.httpdate
|
|
28
|
+
when NilClass
|
|
29
|
+
return
|
|
30
|
+
when CR_OR_LF
|
|
31
|
+
raise InvalidHeader, "Header contains CR or LF"
|
|
32
|
+
else
|
|
33
|
+
value.to_s
|
|
34
|
+
end
|
|
35
|
+
@out << HEADER_FORMAT % [key, value]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def has_key?(key)
|
|
40
|
+
@sent[key.downcase]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
@out.join
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
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
|
+
"#{timestamp} #{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,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../server"
|
|
4
|
+
|
|
5
|
+
module Thin
|
|
6
|
+
module Rackup
|
|
7
|
+
class Handler
|
|
8
|
+
def self.run(app, **options)
|
|
9
|
+
environment = ENV['RACK_ENV'] || 'development'
|
|
10
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
|
11
|
+
|
|
12
|
+
host = options.delete(:Host) || default_host
|
|
13
|
+
port = options.delete(:Port) || 8080
|
|
14
|
+
args = [host, port, app, options]
|
|
15
|
+
|
|
16
|
+
server = ::Thin::Server.new(*args)
|
|
17
|
+
yield server if block_given?
|
|
18
|
+
|
|
19
|
+
server.start
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.valid_options
|
|
23
|
+
environment = ENV['RACK_ENV'] || 'development'
|
|
24
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
|
28
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require 'tempfile'
|
|
2
|
+
require_relative './env'
|
|
3
|
+
|
|
4
|
+
module Thin
|
|
5
|
+
# Raised when an incoming request is not valid
|
|
6
|
+
# and the server can not process it.
|
|
7
|
+
class InvalidRequest < IOError; end
|
|
8
|
+
|
|
9
|
+
# A request sent by the client to the server.
|
|
10
|
+
class Request
|
|
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
|
+
MAX_HEADER = 1024 * (80 + 32)
|
|
16
|
+
|
|
17
|
+
INITIAL_BODY = String.new
|
|
18
|
+
# Force external_encoding of request's body to ASCII_8BIT
|
|
19
|
+
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!) && defined?(Encoding::ASCII_8BIT)
|
|
20
|
+
|
|
21
|
+
# Freeze some HTTP header names & values
|
|
22
|
+
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
|
23
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
|
24
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
|
25
|
+
LOCALHOST = 'localhost'.freeze
|
|
26
|
+
REQUEST_HTTP_VERSION = 'thin.request_http_version'.freeze
|
|
27
|
+
HTTP_1_0 = 'HTTP/1.0'.freeze
|
|
28
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
|
29
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
|
30
|
+
CONNECTION = 'HTTP_CONNECTION'.freeze
|
|
31
|
+
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
|
|
32
|
+
CLOSE_REGEXP = /\bclose\b/i.freeze
|
|
33
|
+
HEAD = 'HEAD'.freeze
|
|
34
|
+
|
|
35
|
+
# Freeze some Rack header names
|
|
36
|
+
RACK_INPUT = 'rack.input'.freeze
|
|
37
|
+
RACK_VERSION = 'rack.version'.freeze
|
|
38
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
|
39
|
+
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
|
40
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
|
41
|
+
RACK_RUN_ONCE = 'rack.run_once'.freeze
|
|
42
|
+
ASYNC_CALLBACK = 'async.callback'.freeze
|
|
43
|
+
ASYNC_CLOSE = 'async.close'.freeze
|
|
44
|
+
|
|
45
|
+
# CGI-like request environment variables
|
|
46
|
+
attr_reader :env
|
|
47
|
+
|
|
48
|
+
# Unparsed data of the request
|
|
49
|
+
attr_reader :data
|
|
50
|
+
|
|
51
|
+
# Request body
|
|
52
|
+
attr_reader :body
|
|
53
|
+
|
|
54
|
+
def initialize
|
|
55
|
+
@parser = Thin::HttpParser.new
|
|
56
|
+
@data = String.new
|
|
57
|
+
@nparsed = 0
|
|
58
|
+
@body = StringIO.new(INITIAL_BODY.dup)
|
|
59
|
+
@env = Env.with_defaults({
|
|
60
|
+
SERVER_SOFTWARE => SERVER,
|
|
61
|
+
SERVER_NAME => LOCALHOST,
|
|
62
|
+
|
|
63
|
+
# Rack stuff
|
|
64
|
+
RACK_INPUT => @body,
|
|
65
|
+
RACK_ERRORS => STDERR,
|
|
66
|
+
})
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Parse a chunk of data into the request environment
|
|
70
|
+
# Raises an +InvalidRequest+ if invalid.
|
|
71
|
+
# Returns +true+ if the parsing is complete.
|
|
72
|
+
def parse(data)
|
|
73
|
+
if data.size > 0 && finished? # headers and body already fully satisfied. more data is erroneous.
|
|
74
|
+
raise InvalidRequest, 'Content longer than specified'
|
|
75
|
+
elsif @parser.finished? # Header finished, can only be some more body
|
|
76
|
+
@body << data
|
|
77
|
+
else # Parse more header using the super parser
|
|
78
|
+
@data << data
|
|
79
|
+
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
|
|
80
|
+
|
|
81
|
+
@nparsed = @parser.execute(@env, @data, @nparsed)
|
|
82
|
+
|
|
83
|
+
# Transfer to a tempfile if body is very big
|
|
84
|
+
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if finished? # Check if header and body are complete
|
|
89
|
+
@data = nil
|
|
90
|
+
@body.rewind
|
|
91
|
+
true # Request is fully parsed
|
|
92
|
+
else
|
|
93
|
+
false # Not finished, need more data
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# +true+ if headers and body are finished parsing
|
|
98
|
+
def finished?
|
|
99
|
+
@parser.finished? && @body.size >= content_length
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Expected size of the body
|
|
103
|
+
def content_length
|
|
104
|
+
@env[CONTENT_LENGTH].to_i
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns +true+ if the client expects the connection to be persistent.
|
|
108
|
+
def persistent?
|
|
109
|
+
# Clients and servers SHOULD NOT assume that a persistent connection
|
|
110
|
+
# is maintained for HTTP versions less than 1.1 unless it is explicitly
|
|
111
|
+
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
|
|
112
|
+
if @env[REQUEST_HTTP_VERSION] == HTTP_1_0
|
|
113
|
+
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
|
|
114
|
+
|
|
115
|
+
# HTTP/1.1 client intends to maintain a persistent connection unless
|
|
116
|
+
# a Connection header including the connection-token "close" was sent
|
|
117
|
+
# in the request
|
|
118
|
+
else
|
|
119
|
+
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def remote_address=(address)
|
|
124
|
+
@env[REMOTE_ADDR] = address
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def threaded=(value)
|
|
128
|
+
@env[RACK_MULTITHREAD] = value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def async_callback=(callback)
|
|
132
|
+
@env[ASYNC_CALLBACK] = callback
|
|
133
|
+
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def async_close
|
|
137
|
+
@async_close ||= @env[ASYNC_CLOSE]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def head?
|
|
141
|
+
@env[REQUEST_METHOD] == HEAD
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Close any resource used by the request
|
|
145
|
+
def close
|
|
146
|
+
@body.close! if @body.class == Tempfile
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
def move_body_to_tempfile
|
|
151
|
+
current_body = @body
|
|
152
|
+
current_body.rewind
|
|
153
|
+
@body = Tempfile.new(BODY_TMPFILE)
|
|
154
|
+
@body.binmode
|
|
155
|
+
@body << current_body.read
|
|
156
|
+
@env[RACK_INPUT] = @body
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|