/
server.rb
240 lines (206 loc) · 8.14 KB
/
server.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
module Thin
# The uterly famous Thin HTTP server.
# It listen for incoming request through a given +backend+
# and forward all request to +app+.
#
# == TCP server
# Create a new TCP server on bound to <tt>host:port</tt> by specifiying +host+
# and +port+ as the first 2 arguments.
#
# Thin::Server.start('0.0.0.0', 3000, app)
#
# == UNIX domain server
# Create a new UNIX domain socket bound to +socket+ file by specifiying a filename
# as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a <tt>/</tt>
# it will be assumed to be a UNIX socket.
#
# Thin::Server.start('/tmp/thin.sock', app)
#
# == Using a custom backend
# You can implement your own way to connect the server to its client by creating your
# own Backend class and pass it as the :backend option.
#
# Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend)
#
# == Rack application (+app+)
# All requests will be processed through +app+ that must be a valid Rack adapter.
# A valid Rack adapter (application) must respond to <tt>call(env#Hash)</tt> and
# return an array of <tt>[status, headers, body]</tt>.
#
# == Building an app in place
# If a block is passed, a <tt>Rack::Builder</tt> instance
# will be passed to build the +app+. So you can do cool stuff like this:
#
# Thin::Server.start('0.0.0.0', 3000) do
# use Rack::CommonLogger
# use Rack::ShowExceptions
# map "/lobster" do
# use Rack::Lint
# run Rack::Lobster.new
# end
# end
#
# == Controlling with signals
# * QUIT: Gracefull shutdown (see Server#stop)
# * INT and TERM: Force shutdown (see Server#stop!)
# Disable signals by passing <tt>:signals => false</tt>
#
class Server
include Logging
include Daemonizable
extend Forwardable
# Default values
DEFAULT_TIMEOUT = 30 #sec
DEFAULT_HOST = '0.0.0.0'
DEFAULT_PORT = 3000
DEFAULT_MAXIMUM_CONNECTIONS = 1024
DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 512
# Application (Rack adapter) called with the request that produces the response.
attr_accessor :app
# Backend handling the connections to the clients.
attr_accessor :backend
# Maximum number of seconds for incoming data to arrive before the connection
# is dropped.
def_delegators :backend, :timeout, :timeout=
# Maximum number of file or socket descriptors that the server may open.
def_delegators :backend, :maximum_connections, :maximum_connections=
# Maximum number of connection that can be persistent at the same time.
# Most browser never close the connection so most of the time they are closed
# when the timeout occur. If we don't control the number of persistent connection,
# if would be very easy to overflow the server for a DoS attack.
def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
# Allow using threads in the backend.
def_delegators :backend, :threaded?, :threaded=
# Address and port on which the server is listening for connections.
def_delegators :backend, :host, :port
# UNIX domain socket on which the server is listening for connections.
def_delegator :backend, :socket
# Disable the use of epoll under Linux
def_delegators :backend, :no_epoll, :no_epoll=
def initialize(*args, &block)
host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
args.each do |arg|
case arg
when Fixnum, /^\d+$/ then port = arg.to_i
when String then host = arg
when Hash then options = arg
else
@app = arg if arg.respond_to?(:call)
end
end
# Try to intelligently select which backend to use.
@backend = select_backend(host, port, options)
load_cgi_multipart_eof_fix
@backend.server = self
# Set defaults
@backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
@backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
@backend.timeout = DEFAULT_TIMEOUT
# Allow using Rack builder as a block
@app = Rack::Builder.new(&block).to_app if block
# If in debug mode, wrap in logger adapter
@app = Rack::CommonLogger.new(@app) if Logging.debug?
setup_signals unless options[:signals].class == FalseClass
end
# Lil' shortcut to turn this:
#
# Server.new(...).start
#
# into this:
#
# Server.start(...)
#
def self.start(*args, &block)
new(*args, &block).start!
end
# Start the server and listen for connections.
def start
raise ArgumentError, 'app required' unless @app
log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
debug ">> Debugging ON"
trace ">> Tracing ON"
log ">> Threaded mode #{@backend.threaded? ? 'ON' : 'OFF'}"
log ">> Maximum connections set to #{@backend.maximum_connections}"
log ">> Listening on #{@backend}, CTRL+C to stop"
@backend.start
end
alias :start! :start
# == Gracefull shutdown
# Stops the server after processing all current connections.
# As soon as this method is called, the server stops accepting
# new requests and wait for all current connections to finish.
# Calling twice is the equivalent of calling <tt>stop!</tt>.
def stop
if running?
@backend.stop
unless @backend.empty?
log ">> Waiting for #{@backend.size} connection(s) to finish, " +
"can take up to #{timeout} sec, CTRL+C to stop now"
end
else
stop!
end
end
# == Force shutdown
# Stops the server closing all current connections right away.
# This doesn't wait for connection to finish their work and send data.
# All current requests will be dropped.
def stop!
log ">> Stopping ..."
@backend.stop!
end
# == Configure the server
# The process might need to have superuser privilege to configure
# server with optimal options.
def config
@backend.config
end
# Name of the server and type of backend used.
# This is also the name of the process in which Thin is running as a daemon.
def name
"thin server (#{@backend})"
end
alias :to_s :name
# Return +true+ if the server is running and ready to receive requests.
# Note that the server might still be running and return +false+ when
# shuting down and waiting for active connections to complete.
def running?
@backend.running?
end
protected
# Register signals:
# * INT calls +stop+ to shutdown gracefully.
# * TERM calls <tt>stop!</tt> to force shutdown.
def setup_signals
trap('QUIT') { stop } unless Thin.win?
trap('INT') { stop! }
trap('TERM') { stop! }
end
def select_backend(host, port, options)
case
when options.has_key?(:backend)
raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
options[:backend].new(host, port, options)
when options.has_key?(:swiftiply)
Backends::SwiftiplyClient.new(host, port, options)
when host.include?('/')
Backends::UnixServer.new(host)
else
Backends::TcpServer.new(host, port)
end
end
# Taken from Mongrel cgi_multipart_eof_fix
# Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
def load_cgi_multipart_eof_fix
version = RUBY_VERSION.split('.').map { |i| i.to_i }
if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
begin
require 'cgi_multipart_eof_fix'
rescue LoadError
log "!! Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
log " gem install cgi_multipart_eof_fix"
end
end
end
end
end