prefork_engine 0.0.1 → 0.0.2
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 +4 -4
- data/Changes +4 -0
- data/example/config.ru +13 -0
- data/example/starlet.rb +306 -0
- data/lib/prefork_engine/version.rb +1 -1
- data/prefork_engine.gemspec +0 -1
- metadata +5 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28b3d832f86df0ffd4cc4f50995409fdf6e5fa03
|
4
|
+
data.tar.gz: 7cccd2f8bf896a14e5695616cf745b86c0905c10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ca812d9907a61cfe97d6199d3c3f72c56b4e92ef82a34b1748933cb10affecac50081d1cf0e1235e9a173323f3bebae218e6c7150661bfd9cd1b08bfa2ca1a9
|
7
|
+
data.tar.gz: 772d6caa397e481338a53018fe948f83172cffbf52bcc057190e4d963acf427a9a3498d040e54daff91c9b7a8b1cb4858bca24cedae3c3e40027f58586e89b4a
|
data/Changes
ADDED
data/example/config.ru
ADDED
data/example/starlet.rb
ADDED
@@ -0,0 +1,306 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# sample Rack handler using PreforkEngine
|
4
|
+
# This is ruby port of Starlet (https://metacpan.org/pod/Starlet)
|
5
|
+
#
|
6
|
+
# ## How to use
|
7
|
+
# $ gem install pico_http_parser
|
8
|
+
# $ gem isntall prefork_engine
|
9
|
+
# $ cat config.ru
|
10
|
+
# class HelloApp
|
11
|
+
# def call(env)
|
12
|
+
# [
|
13
|
+
# 200,
|
14
|
+
# { 'Content-Type' => 'text/html' },
|
15
|
+
# ['hello world ']
|
16
|
+
# ]
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# run HelloApp.new
|
21
|
+
#
|
22
|
+
# $ rackup -r ./starlet.rb -E production -s Starlet \
|
23
|
+
# -O MaxWorkers=5 -O MaxRequestPerChild=1000 -O MinRequestPerChild=800 config.ru
|
24
|
+
#
|
25
|
+
# ## LICENSE
|
26
|
+
# This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
|
27
|
+
# See http://www.perl.com/perl/misc/Artistic.html
|
28
|
+
|
29
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
30
|
+
require 'rubygems'
|
31
|
+
require 'rack'
|
32
|
+
require 'stringio'
|
33
|
+
require 'socket'
|
34
|
+
require 'rack/utils'
|
35
|
+
require 'io/nonblock'
|
36
|
+
require 'prefork_engine'
|
37
|
+
require 'pico_http_parser'
|
38
|
+
|
39
|
+
module Rack
|
40
|
+
module Handler
|
41
|
+
class Starlet
|
42
|
+
DEFAULT_OPTIONS = {
|
43
|
+
:Host => '0.0.0.0',
|
44
|
+
:Port => 9292,
|
45
|
+
:MaxWorkers => 10,
|
46
|
+
:Timeout => 300,
|
47
|
+
:MaxRequestPerChild => 100,
|
48
|
+
:MinRequestPerChild => nil,
|
49
|
+
:SpawnInterval => nil,
|
50
|
+
:ErrRespawnInterval => nil
|
51
|
+
}
|
52
|
+
NULLIO = StringIO.new("").set_encoding('BINARY')
|
53
|
+
|
54
|
+
def self.run(app, options={})
|
55
|
+
slf = new(options)
|
56
|
+
slf.setup_listener()
|
57
|
+
slf.run_worker(app)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.valid_options
|
61
|
+
{
|
62
|
+
"Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
|
63
|
+
"Port=PORT" => "Port to listen on (default: 9292)",
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(options={})
|
68
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
69
|
+
@server = nil
|
70
|
+
@_is_tcp = false
|
71
|
+
@_using_defer_accept = false
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup_listener()
|
76
|
+
if ENV["SERVER_STARTER_PORT"] then
|
77
|
+
hostport, fd = ENV["SERVER_STARTER_PORT"].split("=",2)
|
78
|
+
if m = hostport.match(/(.*):(\d+)/) then
|
79
|
+
@options[:Host] = m[0]
|
80
|
+
@options[:Port] = m[1].to_i
|
81
|
+
else
|
82
|
+
@options[:Port] = hostport
|
83
|
+
end
|
84
|
+
@server = TCPServer.for_fd(fd.to_i)
|
85
|
+
@_is_tcp = true if !@server.local_address.unix?
|
86
|
+
end
|
87
|
+
|
88
|
+
if @server == nil
|
89
|
+
@server = TCPServer.new(@options[:Host], @options[:Port])
|
90
|
+
@server.setsockopt(:SOCKET, :REUSEADDR, 1)
|
91
|
+
@_is_tcp = true
|
92
|
+
end
|
93
|
+
|
94
|
+
if RUBY_PLATFORM.match(/linux/) && @_is_tcp == true then
|
95
|
+
begin
|
96
|
+
@server.setsockopt(Socket::IPPROTO_TCP, 9, 1)
|
97
|
+
@_using_defer_accept = true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def run_worker(app)
|
103
|
+
pm_args = {
|
104
|
+
"max_workers" => @options[:MaxWorkers].to_i,
|
105
|
+
"trap_signals" => {
|
106
|
+
"TERM" => 'TERM',
|
107
|
+
"HUP" => 'TERM',
|
108
|
+
},
|
109
|
+
}
|
110
|
+
if @options[:SpawnInterval] then
|
111
|
+
pm_args["trap_signals"]["USR1"] = ["TERM", @options[:SpawnInterval].to_i]
|
112
|
+
pm_args["spawn_interval"] = @options[:SpawnInterval].to_i
|
113
|
+
end
|
114
|
+
if @options[:ErrRespawnInterval] then
|
115
|
+
pm_args["err_respawn_interval"] = @options[:ErrRespawnInterval].to_i
|
116
|
+
end
|
117
|
+
pe = PreforkEngine.new(pm_args)
|
118
|
+
while !pe.signal_received.match(/^(TERM|USR1)$/)
|
119
|
+
pe.start {
|
120
|
+
srand
|
121
|
+
self.accept_loop(app)
|
122
|
+
}
|
123
|
+
end
|
124
|
+
pe.wait_all_children
|
125
|
+
end
|
126
|
+
|
127
|
+
def _calc_reqs_per_child
|
128
|
+
max = @options[:MaxRequestPerChild].to_i
|
129
|
+
if min = @options[:MinRequestPerChild] then
|
130
|
+
return (max - (max - min.to_i + 1) * rand).to_i
|
131
|
+
end
|
132
|
+
return max.to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
def accept_loop(app)
|
136
|
+
@can_exit = true
|
137
|
+
@term_received = 0
|
138
|
+
proc_req_count = 0
|
139
|
+
|
140
|
+
Signal.trap('TERM') {
|
141
|
+
if @can_exit then
|
142
|
+
exit!(true)
|
143
|
+
end
|
144
|
+
@term_received += 1
|
145
|
+
if @can_exit || @term_received > 1 then
|
146
|
+
exit!(true)
|
147
|
+
end
|
148
|
+
}
|
149
|
+
Signal.trap('PIPE', 'IGNORE')
|
150
|
+
max_reqs = self._calc_reqs_per_child()
|
151
|
+
while proc_req_count < max_reqs
|
152
|
+
@can_exit = true
|
153
|
+
connection = @server.accept
|
154
|
+
begin
|
155
|
+
connection.nonblock(true) {
|
156
|
+
peeraddr = nil
|
157
|
+
peerport = 0
|
158
|
+
if @_is_tcp then
|
159
|
+
connection.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
160
|
+
peer = connection.peeraddr
|
161
|
+
peeraddr = peer[2],
|
162
|
+
peerport = peer[1].to_s
|
163
|
+
end
|
164
|
+
proc_req_count += 1
|
165
|
+
@_is_deferred_accept = @_using_defer_accept
|
166
|
+
env = {
|
167
|
+
'SERVER_NAME' => @options[:Host],
|
168
|
+
'SERVER_PORT' => @options[:Port].to_s,
|
169
|
+
'REMOTE_ADDR' => peeraddr,
|
170
|
+
'REMOTE_PORT' => peerport,
|
171
|
+
'rack.version' => [0,1],
|
172
|
+
'rack.errors' => STDERR,
|
173
|
+
'rack.multithread' => false,
|
174
|
+
'rack.multiprocess' => false,
|
175
|
+
'rack.run_once' => false,
|
176
|
+
'rack.url_scheme' => 'http'
|
177
|
+
}
|
178
|
+
self.handle_connection(env, connection, app)
|
179
|
+
}
|
180
|
+
ensure
|
181
|
+
connection.close
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def handle_connection(env, connection, app)
|
187
|
+
buf = ""
|
188
|
+
while true
|
189
|
+
readed = self.read_timeout(connection)
|
190
|
+
if readed == nil then
|
191
|
+
next
|
192
|
+
end
|
193
|
+
@can_exit = false
|
194
|
+
buf += readed
|
195
|
+
reqlen = PicoHTTPParser.parse_http_request(buf,env)
|
196
|
+
if reqlen >= 0 then
|
197
|
+
# force donwgrade to 1.0
|
198
|
+
env["SERVER_PROTOCOL"] = "HTTP/1.0"
|
199
|
+
# handle request
|
200
|
+
if (cl = env["CONTENT_LENGTH"].to_i) > 0 then
|
201
|
+
buf = buf.unpack('C*').slice(reqlen..-1).pack('C*')
|
202
|
+
buffer = StringIO.new("").set_encoding('BINARY')
|
203
|
+
while cl > 0
|
204
|
+
chunk = ""
|
205
|
+
if buf.bytesize > 0 then
|
206
|
+
chunk = buf
|
207
|
+
buf = ""
|
208
|
+
else
|
209
|
+
readed = self.read_timeout(connection)
|
210
|
+
if readed == nil then
|
211
|
+
return
|
212
|
+
end
|
213
|
+
chunk += readed
|
214
|
+
end
|
215
|
+
buffer << chunk
|
216
|
+
cl -= chunk.bytesize
|
217
|
+
end
|
218
|
+
buffer.rewind
|
219
|
+
env["rack.input"] = buffer
|
220
|
+
else
|
221
|
+
env["rack.input"] = NULLIO
|
222
|
+
end
|
223
|
+
status, header, body = app.call(env)
|
224
|
+
res_header = "HTTP/1.0 "+status.to_s+" "+Rack::Utils::HTTP_STATUS_CODES[status]+"\r\nConnection: close\r\n"
|
225
|
+
header.each do |k,vs|
|
226
|
+
if k.downcase == "connection" then
|
227
|
+
next
|
228
|
+
end
|
229
|
+
res_header += k + ": " + vs + "\r\n"
|
230
|
+
end
|
231
|
+
res_header += "\r\n"
|
232
|
+
if body.length == 1 && body[0].bytesize < 40960 then
|
233
|
+
ret = self.write_all(connection,res_header+body[0])
|
234
|
+
else
|
235
|
+
ret = self.write_all(connection,res_header)
|
236
|
+
body.each do |part|
|
237
|
+
self.write_all(connection,part)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
return true
|
241
|
+
elsif reqlen == -2 then
|
242
|
+
# request is incomplete, do nothing
|
243
|
+
else
|
244
|
+
# error
|
245
|
+
return nil
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def read_timeout(connection)
|
251
|
+
if @_is_deferred_accept then
|
252
|
+
@_is_deferred_accept = false
|
253
|
+
else
|
254
|
+
if !IO.select([connection],nil,nil,300) then
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
end
|
258
|
+
while true
|
259
|
+
begin
|
260
|
+
buf = connection.sysread(4096)
|
261
|
+
return buf
|
262
|
+
rescue IO::WaitReadable
|
263
|
+
# retry
|
264
|
+
break
|
265
|
+
rescue EOFError
|
266
|
+
# closed
|
267
|
+
return nil
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def write_timeout(connection, buf)
|
273
|
+
while true
|
274
|
+
begin
|
275
|
+
len = connection.syswrite(buf)
|
276
|
+
return len
|
277
|
+
rescue IO::WaitReadable
|
278
|
+
# retry
|
279
|
+
break
|
280
|
+
rescue EOFError
|
281
|
+
# closed
|
282
|
+
return nil
|
283
|
+
end
|
284
|
+
end
|
285
|
+
if !IO.select(nil,[connection],nil,300) then
|
286
|
+
return nil
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def write_all(connection, buf)
|
291
|
+
off = 0
|
292
|
+
while buf.bytesize - off > 0
|
293
|
+
ret = self.write_timeout(connection,buf.unpack('C*').slice(off..-1).pack('C*'))
|
294
|
+
if ret == nil then
|
295
|
+
return nil
|
296
|
+
end
|
297
|
+
off += ret
|
298
|
+
end
|
299
|
+
return buf.bytesize
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
|
data/prefork_engine.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prefork_engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masahiro Nagano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pico_http_parser
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 0.0.3
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 0.0.3
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: proc-wait3
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,10 +76,13 @@ files:
|
|
90
76
|
- ".gitignore"
|
91
77
|
- ".rspec"
|
92
78
|
- ".travis.yml"
|
79
|
+
- Changes
|
93
80
|
- Gemfile
|
94
81
|
- LICENSE.txt
|
95
82
|
- README.md
|
96
83
|
- Rakefile
|
84
|
+
- example/config.ru
|
85
|
+
- example/starlet.rb
|
97
86
|
- lib/prefork_engine.rb
|
98
87
|
- lib/prefork_engine/version.rb
|
99
88
|
- prefork_engine.gemspec
|