pmux-gw 0.1.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/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/Makefile +18 -0
- data/README.md +145 -0
- data/Rakefile +4 -0
- data/bin/pmux-gw +6 -0
- data/examples/password +4 -0
- data/examples/pmux-gw.conf +15 -0
- data/lib/pmux-gw/application.rb +226 -0
- data/lib/pmux-gw/client_context.rb +40 -0
- data/lib/pmux-gw/history.rb +149 -0
- data/lib/pmux-gw/http_handler.rb +454 -0
- data/lib/pmux-gw/logger_wrapper.rb +75 -0
- data/lib/pmux-gw/pmux_handler.rb +86 -0
- data/lib/pmux-gw/static/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-icons_222222_256x240.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-icons_454545_256x240.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-icons_888888_256x240.png +0 -0
- data/lib/pmux-gw/static/css/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/pmux-gw/static/css/jquery-ui-1.9.2.custom.css +462 -0
- data/lib/pmux-gw/static/js/jquery-1.8.3.js +9472 -0
- data/lib/pmux-gw/static/js/jquery-ui-1.9.2.custom.js +14879 -0
- data/lib/pmux-gw/syslog_wrapper.rb +58 -0
- data/lib/pmux-gw/template/history.tmpl +101 -0
- data/lib/pmux-gw/version.rb +5 -0
- data/lib/pmux-gw.rb +12 -0
- data/pmux-gw.gemspec +26 -0
- data/rpm/Makefile +19 -0
- data/rpm/pmux-gw.spec +100 -0
- metadata +173 -0
@@ -0,0 +1,454 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'date'
|
3
|
+
require 'cgi'
|
4
|
+
require 'base64'
|
5
|
+
require 'erb'
|
6
|
+
require 'eventmachine'
|
7
|
+
require 'evma_httpserver'
|
8
|
+
require 'em_pessimistic'
|
9
|
+
|
10
|
+
module Pmux
|
11
|
+
module Gateway
|
12
|
+
class HttpHandler < EM::Connection
|
13
|
+
include EM::HttpServer
|
14
|
+
|
15
|
+
@@static_resources = [ "/css", "/js", "/img" ]
|
16
|
+
@@param_attr = [
|
17
|
+
{ "name" => "mapper", "multi" => false, "required" => true, "gwoptonly" => false, "default" => nil },
|
18
|
+
{ "name" => "ship-file", "multi" => true, "required" => false, "gwoptonly" => false, "default" => nil },
|
19
|
+
{ "name" => "file", "multi" => true, "required" => false, "gwoptonly" => false, "default" => nil },
|
20
|
+
{ "name" => "file-glob", "multi" => true, "required" => false, "gwoptonly" => false, "default" => nil },
|
21
|
+
{ "name" => "reducer", "multi" => false, "required" => false, "gwoptonly" => false, "default" => nil },
|
22
|
+
{ "name" => "num-r", "multi" => false, "required" => false, "gwoptonly" => false, "default" => nil },
|
23
|
+
{ "name" => "ff", "multi" => false, "required" => false, "gwoptonly" => false, "default" => nil },
|
24
|
+
{ "name" => "storage", "multi" => false, "required" => false, "gwoptonly" => false, "default" => ["glusterfs"] },
|
25
|
+
{ "name" => "locator-host", "multi" => false, "required" => false, "gwoptonly" => false, "default" => ["127.0.0.1"] },
|
26
|
+
{ "name" => "locator-port", "multi" => false, "required" => false, "gwoptonly" => false, "default" => nil },
|
27
|
+
{ "name" => "detect-error", "multi" => false, "required" => false, "gwoptonly" => true, "default" => nil }
|
28
|
+
]
|
29
|
+
@@ext_mimetype_map = {
|
30
|
+
".js" => "text/javascript",
|
31
|
+
".css" => "text/css",
|
32
|
+
".png" => "text/png"
|
33
|
+
}
|
34
|
+
@@date_format = "%Y/%m/%d"
|
35
|
+
@@term = false
|
36
|
+
@@task_cnt = 0
|
37
|
+
@@history_template = nil
|
38
|
+
|
39
|
+
def self.get_task_cnt
|
40
|
+
return @@task_cnt
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.set_term
|
44
|
+
@@term = true
|
45
|
+
end
|
46
|
+
|
47
|
+
# httpのリクエストを受けて、pmuxとの橋渡しをするクラス
|
48
|
+
def initialize *args
|
49
|
+
super
|
50
|
+
@resource_map = {
|
51
|
+
"/pmux" => method(:resource_pmux),
|
52
|
+
"/existence" => method(:resource_existence),
|
53
|
+
"/history" => method(:resource_history),
|
54
|
+
"/task" => method(:resource_task)
|
55
|
+
}
|
56
|
+
@logger = LoggerWrapper.instance()
|
57
|
+
@history = History.instance()
|
58
|
+
@auth_user = nil
|
59
|
+
@volume_prefix = nil
|
60
|
+
@cc = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def post_init
|
64
|
+
super
|
65
|
+
no_environment_strings
|
66
|
+
@logger.logging("debug", "connected to client")
|
67
|
+
end
|
68
|
+
|
69
|
+
def unbind
|
70
|
+
super
|
71
|
+
@logger.logging("debug", "client is closed")
|
72
|
+
if !@cc.nil?
|
73
|
+
@@task_cnt -= 1
|
74
|
+
@cc.end_datetime = DateTime.now().new_offset(Rational(9, 24))
|
75
|
+
# pmuxが終わっていないのにクライアントとの接続が切れたらpmuxを終了する
|
76
|
+
if !@cc.pmux_terminated
|
77
|
+
@cc.pmux_handler.close_connection()
|
78
|
+
@cc.force_pmux_terminated = true
|
79
|
+
@cc.status = "disconnected"
|
80
|
+
else
|
81
|
+
@cc.status = "done"
|
82
|
+
end
|
83
|
+
@history.save(@cc)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_http_headers
|
88
|
+
@headers = {}
|
89
|
+
for line in @http_headers.split("\000")
|
90
|
+
key, value = line.split(":", 2)
|
91
|
+
@headers[key.strip().downcase()] = value.strip()
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_auth
|
96
|
+
auth = nil
|
97
|
+
if @headers.key?("authorization")
|
98
|
+
auth = @headers["authorization"]
|
99
|
+
elsif @headers.key?("proxy-authorization")
|
100
|
+
auth = @headers["proxy-authorization"]
|
101
|
+
end
|
102
|
+
return false if auth.nil?
|
103
|
+
type, string = auth.split(' ')
|
104
|
+
return false if type.downcase() != "basic"
|
105
|
+
@auth_user, @auth_pass = Base64.decode64(string).split(':', 2)
|
106
|
+
return true
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_param_values(name, default)
|
110
|
+
return @params[name] if @params.key?(name) && @params[name].length > 0
|
111
|
+
return default
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_param_value(name, default)
|
115
|
+
vals = get_param_values(name, default)
|
116
|
+
return vals[0] if !vals.nil?
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_user_volume_prefix
|
121
|
+
if $userdb.key?(@auth_user) && $userdb[@auth_user]["password"] == @auth_pass
|
122
|
+
@volume_prefix = $userdb[@auth_user]["volume-prefix"]
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
|
128
|
+
def is_detect_error
|
129
|
+
# エラー検出モードかどうかの判定
|
130
|
+
if @detect_error.nil?
|
131
|
+
if get_param_value("detect-error", ["off"]) == "on"
|
132
|
+
@logger.logging("debug", "mode is decect-error")
|
133
|
+
return true
|
134
|
+
end
|
135
|
+
return false
|
136
|
+
else
|
137
|
+
return @detect_error
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def build_command
|
142
|
+
# パラメータからpmuxコマンドの生成する
|
143
|
+
mapper = nil
|
144
|
+
opt_list = ["/usr/local/bin/ruby193", "/usr/local/bin/pmux"]
|
145
|
+
file_list = []
|
146
|
+
for attr in @@param_attr
|
147
|
+
if !attr["multi"]
|
148
|
+
val = get_param_value(attr["name"], attr["default"])
|
149
|
+
else
|
150
|
+
val = get_param_values(attr["name"], attr["default"])
|
151
|
+
end
|
152
|
+
if attr["required"]
|
153
|
+
if val.nil?
|
154
|
+
@logger.logging("info", "patameter error #{attr['name']}")
|
155
|
+
return nil, nil, "#{attr['name']} is required"
|
156
|
+
else
|
157
|
+
if attr["name"] == "mapper"
|
158
|
+
mapper = val
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
if !val.nil? && !attr["gwoptonly"]
|
163
|
+
if !attr["multi"]
|
164
|
+
opt_list << "--#{attr['name']}=#{val}"
|
165
|
+
elsif attr["name"] == 'file'
|
166
|
+
for f in val
|
167
|
+
f = [$config["glusterfs_root"], @volume_prefix, f].join(File::SEPARATOR)
|
168
|
+
file_list << f
|
169
|
+
end
|
170
|
+
elsif attr["name"] == 'file-glob'
|
171
|
+
for fg in val
|
172
|
+
fg = [$config["glusterfs_root"], @volume_prefix, fg].join(File::SEPARATOR)
|
173
|
+
file_list << Dir.glob(fg)
|
174
|
+
end
|
175
|
+
elsif attr["name"] == 'ship-file'
|
176
|
+
for f in val
|
177
|
+
f = [$config["glusterfs_root"], @volume_prefix, f].join(File::SEPARATOR)
|
178
|
+
opt_list << "--#{attr['name']}=#{f}"
|
179
|
+
end
|
180
|
+
else
|
181
|
+
for v in val
|
182
|
+
opt_list << "--#{attr['name']}=#{v}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return nil, nil, "file of file-glob is required" if file_list.length < 1
|
188
|
+
opt_list << file_list
|
189
|
+
@logger.logging("debug", opt_list.join(" "))
|
190
|
+
return [ mapper, opt_list.join(" "), nil ]
|
191
|
+
end
|
192
|
+
|
193
|
+
def base_response(args)
|
194
|
+
@response.status = args[:status]
|
195
|
+
@response.content_type(args[:content_type])
|
196
|
+
@response.content = args[:content]
|
197
|
+
@response.send_response
|
198
|
+
@logger.logging("info", "(response) peer: #{@peer_ip} - #{@peer_port}, path: #{@http_path_info}, response: #{@response.status}")
|
199
|
+
end
|
200
|
+
|
201
|
+
def success_response(args = {:status => 200, :content_type => "text/html", :content => ""})
|
202
|
+
base_response(args)
|
203
|
+
end
|
204
|
+
|
205
|
+
def error_response(args = {:status => 400, :content_type => "text/html", :content => ""})
|
206
|
+
base_response(args)
|
207
|
+
end
|
208
|
+
|
209
|
+
def resource_static path
|
210
|
+
@logger.logging("debug", "requested static resource (#{path})")
|
211
|
+
ext = File.extname(path)
|
212
|
+
if !@@ext_mimetype_map.key?(ext)
|
213
|
+
error_response({:status => 404,
|
214
|
+
:content_type => "text/plain",
|
215
|
+
:content => "not found #{@http_path_info} resource"})
|
216
|
+
return
|
217
|
+
end
|
218
|
+
f = nil
|
219
|
+
begin
|
220
|
+
f = open(path)
|
221
|
+
success_response({:status => 200,
|
222
|
+
:content_type => @@ext_mimetype_map[ext],
|
223
|
+
:content => f.read})
|
224
|
+
rescue Exception
|
225
|
+
error_response({:status => 404,
|
226
|
+
:content_type => "text/plain",
|
227
|
+
:content => "not found #{@http_path_info} resource"})
|
228
|
+
ensure
|
229
|
+
if !f.nil?
|
230
|
+
f.close()
|
231
|
+
end
|
232
|
+
end
|
233
|
+
return
|
234
|
+
end
|
235
|
+
|
236
|
+
def resource_pmux
|
237
|
+
# task数が閾値を超えていた場合はbusyを返す
|
238
|
+
if @@task_cnt >= $config["max_tasks"]
|
239
|
+
error_response({:status => 503,
|
240
|
+
:content_type => "text/plain",
|
241
|
+
:content => "number of tasks exceeded the limit"})
|
242
|
+
return
|
243
|
+
end
|
244
|
+
|
245
|
+
# 認証処理
|
246
|
+
if $config["use_basic_auth"]
|
247
|
+
if @auth_user.nil?
|
248
|
+
error_response({:status => 401,
|
249
|
+
:content_type => "text/plain",
|
250
|
+
:content => "need user authentication"})
|
251
|
+
return
|
252
|
+
end
|
253
|
+
if !get_user_volume_prefix()
|
254
|
+
error_response({:status => 403,
|
255
|
+
:content_type => "text/plain",
|
256
|
+
:content => "user authentication failure"})
|
257
|
+
return
|
258
|
+
end
|
259
|
+
end
|
260
|
+
@volume_prefix = "" if @volume_prefix.nil?
|
261
|
+
|
262
|
+
# リソース/pmuxが呼ばれた際の処理
|
263
|
+
if @http_protocol == "HTTP/1.0" && !is_detect_error()
|
264
|
+
error_response({:status => 400,
|
265
|
+
:content_type => "text/plain",
|
266
|
+
:content => "HTTP/1.0 is not support chunked"})
|
267
|
+
return
|
268
|
+
end
|
269
|
+
mapper, cmd, err = build_command()
|
270
|
+
if mapper.nil? || cmd.nil?
|
271
|
+
error_response({:status => 400,
|
272
|
+
:content_type => "text/plain",
|
273
|
+
:content => err})
|
274
|
+
return
|
275
|
+
end
|
276
|
+
@cc = ClientContext.new(self, @response, mapper, cmd, is_detect_error())
|
277
|
+
@cc.set_pmux_handler(EMPessimistic.popen3(cmd, PmuxHandler, @cc))
|
278
|
+
@@task_cnt += 1
|
279
|
+
#ステータス情報を保存しておく
|
280
|
+
@cc.pid = @cc.pmux_handler.get_status.pid
|
281
|
+
@cc.status = "runnning"
|
282
|
+
@cc.start_datetime = DateTime.now().new_offset(Rational(9, 24))
|
283
|
+
@cc.peername = "#{@peer_ip} - #{@peer_port}"
|
284
|
+
@cc.user = @auth_user
|
285
|
+
#ヒストリー情報を更新する
|
286
|
+
@history.save(@cc)
|
287
|
+
end
|
288
|
+
|
289
|
+
def resource_existence
|
290
|
+
# リソース/existenceが呼ばれた際の処理
|
291
|
+
success_response({:status => 204,
|
292
|
+
:content_type => "text/html",
|
293
|
+
:content => ""})
|
294
|
+
end
|
295
|
+
|
296
|
+
def resource_history
|
297
|
+
default_start_date = default_end_date = Date.today()
|
298
|
+
client = get_param_value("client", [""])
|
299
|
+
pid = get_param_value("pid", [""])
|
300
|
+
mapper = get_param_value("mapper", [""])
|
301
|
+
start_date_str = get_param_value("start-date", [default_start_date.strftime(@@date_format)])
|
302
|
+
end_date_str = get_param_value("end-date", [default_end_date.strftime(@@date_format)])
|
303
|
+
begin
|
304
|
+
start_date = Date.strptime(start_date_str, @@date_format)
|
305
|
+
end_date = Date.strptime(end_date_str, @@date_format)
|
306
|
+
# end dateの方が過去の時間を指定されたら、start dateとend dateを自動的に入れ替える
|
307
|
+
if (end_date - start_date).to_i < 0
|
308
|
+
tmp_date = start_date
|
309
|
+
start_date = end_date
|
310
|
+
end_date = tmp_date
|
311
|
+
end
|
312
|
+
rescue ArgumentError
|
313
|
+
# start dateとend dateがパースできない場合はデフォルトにする
|
314
|
+
logger.logging("info", "can not parse date format")
|
315
|
+
start_date = default_start_date
|
316
|
+
end_date = default_end_date
|
317
|
+
end
|
318
|
+
history, history_id_order = @history.load(client, pid, mapper, "", start_date, end_date, false, true)
|
319
|
+
labels = ["date", "client", "user", "pid", "mapper", "start", "end", "elapsed", "status", "detail" ]
|
320
|
+
# templateがまだ読み込まれてなければtemplateを読み込む
|
321
|
+
if @@history_template.nil?
|
322
|
+
begin
|
323
|
+
template_file_path = [File.dirname(__FILE__), "template", "history.tmpl"].join(File::SEPARATOR)
|
324
|
+
@@template = File.read(template_file_path)
|
325
|
+
rescue Exception => e
|
326
|
+
@logger.logging("error", "can not load template file (#{template_file_path}) : #{e}")
|
327
|
+
@logger.logging("error", e.backtrace.join("\n"))
|
328
|
+
success_response({:status => 404,
|
329
|
+
:content_type => "text/html",
|
330
|
+
:content => "can not load template file (#{template_file_path}) : #{e}"})
|
331
|
+
return
|
332
|
+
end
|
333
|
+
end
|
334
|
+
content = ERB.new(@@template, nil, '-').result(binding)
|
335
|
+
success_response({:status => 200,
|
336
|
+
:content_type => "text/html",
|
337
|
+
:content => content})
|
338
|
+
end
|
339
|
+
|
340
|
+
def resource_task
|
341
|
+
client = get_param_value("client", [""])
|
342
|
+
pid = get_param_value("pid", [""])
|
343
|
+
mapper = get_param_value("mapper", [""])
|
344
|
+
start_datetime = get_param_value("start-datetime", [""])
|
345
|
+
date_str = get_param_value("date", [""])
|
346
|
+
if client == "" || pid == "" || mapper == "" || start_datetime == "" || date_str == ""
|
347
|
+
error_response({:status => 400,
|
348
|
+
:content_type => "text/plain",
|
349
|
+
:content => "client and pid and mapper and start-datetime and date is required"})
|
350
|
+
return
|
351
|
+
end
|
352
|
+
begin
|
353
|
+
start_date = Date.parse(date_str)
|
354
|
+
end_date = Date.parse(date_str)
|
355
|
+
rescue ArgumentError
|
356
|
+
error_response({:status => 400,
|
357
|
+
:content_type => "text/plain",
|
358
|
+
:content => "invalid date string"})
|
359
|
+
return
|
360
|
+
end
|
361
|
+
history, history_id_order = @history.load(client, pid, mapper, start_datetime, start_date, end_date, true, false)
|
362
|
+
if history_id_order.length < 1
|
363
|
+
error_response({:status => 404,
|
364
|
+
:content_type => "text/plain",
|
365
|
+
:content => "not found task"})
|
366
|
+
return
|
367
|
+
end
|
368
|
+
elems = history[history_id_order[0]]
|
369
|
+
date = elems.shift()
|
370
|
+
labels = ["client", "user", "pid", "mapper", "start", "end", "elapsed", "status", "command"]
|
371
|
+
content = ""
|
372
|
+
for i in 0..elems.length - 1 do
|
373
|
+
content += "#{labels[i]}\t#{elems[i]}\n";
|
374
|
+
end
|
375
|
+
success_response({:status => 200,
|
376
|
+
:content_type => "text/plain",
|
377
|
+
:content => content})
|
378
|
+
end
|
379
|
+
|
380
|
+
def process_http_request
|
381
|
+
# responseオブジェクトを作る
|
382
|
+
@response = EM::DelegatedHttpResponse.new(self)
|
383
|
+
begin
|
384
|
+
# httpのリクエストを受けた際の処理
|
385
|
+
# tcp keepaliveの設定と、chunkが利用可能かどうかのチェック
|
386
|
+
# 所定のリソースハンドラーへルーティングする処理を行う
|
387
|
+
set_sock_opt(Socket::SOL_SOCKET, 9, 1) # socket.SO_KEEPALIVE
|
388
|
+
set_sock_opt(Socket::SOL_TCP, 4, 1) # socket.TCP_KEEPIDLE
|
389
|
+
set_sock_opt(Socket::SOL_TCP, 5, 1) # socket.TCP_KEEPINTVL
|
390
|
+
set_sock_opt(Socket::SOL_TCP, 6, 10) # socket.TCP_KEEPCNT
|
391
|
+
|
392
|
+
# 終了フラグが立っている場合は503を返す
|
393
|
+
if @@term
|
394
|
+
error_response({:status => 503,
|
395
|
+
:content_type => "text/plain",
|
396
|
+
:content => "apllication is terminating"})
|
397
|
+
return
|
398
|
+
end
|
399
|
+
|
400
|
+
# 接続元情報の取得
|
401
|
+
@peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername())
|
402
|
+
|
403
|
+
# ヘッダ文字列をパースする
|
404
|
+
parse_http_headers()
|
405
|
+
|
406
|
+
# 認証情報の取得
|
407
|
+
get_auth()
|
408
|
+
|
409
|
+
# パラメータのデコード
|
410
|
+
case @http_request_method
|
411
|
+
when "GET"
|
412
|
+
@params = @http_query_string.nil? ? Hash.new : CGI.parse(@http_query_string)
|
413
|
+
when "POST"
|
414
|
+
@params = @http_post_content.nil? ? Hash.new : CGI.parse(@http_post_content)
|
415
|
+
else
|
416
|
+
error_response({:status => 400,
|
417
|
+
:content_type => "text/plain",
|
418
|
+
:content => "unsupport method #{@http_request_method}"})
|
419
|
+
return
|
420
|
+
end
|
421
|
+
|
422
|
+
# 指定されたリソースを処理するメソッドへルーティング
|
423
|
+
@logger.logging("info", "(request) peer: #{@peer_ip} - #{@peer_port}, path: #{@http_path_info}, headers: #{@headers.inspect} params: #{@params.inspect}")
|
424
|
+
|
425
|
+
# staticなリソースの場合
|
426
|
+
for prefix in @@static_resources
|
427
|
+
if !@http_path_info.index(prefix).nil?
|
428
|
+
resource_static([File.dirname(__FILE__), "static", @http_path_info].join(File::SEPARATOR))
|
429
|
+
return
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# そうでない場合
|
434
|
+
if @resource_map.key?(@http_path_info)
|
435
|
+
@resource_map[@http_path_info].call
|
436
|
+
else
|
437
|
+
error_response({:status => 404,
|
438
|
+
:content_type => "text/plain",
|
439
|
+
:content => "not found #{@http_path_info} resource"})
|
440
|
+
return
|
441
|
+
end
|
442
|
+
rescue Exception => e
|
443
|
+
# 予期しない例外は500を返し、ログに残しておく
|
444
|
+
error_response({:status => 500,
|
445
|
+
:content_type => "text/plain",
|
446
|
+
:content => "error: #{e}"})
|
447
|
+
@logger.logging("error", "error occurred in http handler: #{e}")
|
448
|
+
@logger.logging("error", e.backtrace.join("\n"))
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Pmux
|
5
|
+
module Gateway
|
6
|
+
class LoggerWrapper
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
@@log_level_map = {
|
10
|
+
'debug' => Logger::DEBUG,
|
11
|
+
'info' => Logger::INFO,
|
12
|
+
'warn' => Logger::WARN,
|
13
|
+
'error' => Logger::ERROR,
|
14
|
+
'fatal' => Logger::FATAL
|
15
|
+
}
|
16
|
+
|
17
|
+
def init foreground
|
18
|
+
@syslog_wrapper = SyslogWrapper.instance()
|
19
|
+
@foreground = foreground
|
20
|
+
@logger = nil
|
21
|
+
@serverity = Logger::INFO
|
22
|
+
end
|
23
|
+
|
24
|
+
def fixup_level level
|
25
|
+
return level if @@log_level_map.key?(level)
|
26
|
+
return "info"
|
27
|
+
end
|
28
|
+
|
29
|
+
def open log_file_path, log_level
|
30
|
+
# ログをオープンする
|
31
|
+
# すでに開いている状態で呼ばれるとリオープンする
|
32
|
+
@serverity = @@log_level_map[fixup_level(log_level)]
|
33
|
+
old_logger = @logger
|
34
|
+
@logger = nil
|
35
|
+
begin
|
36
|
+
@logger = Logger.new(log_file_path, 'daily')
|
37
|
+
@logger.level = @serverity
|
38
|
+
old_logger.close() if !old_logger.nil?
|
39
|
+
rescue Errno::ENOENT => e
|
40
|
+
@logger = old_logger if !old_logger.nil?
|
41
|
+
logging("error", "not found log file (#{log_file_path})")
|
42
|
+
logging("error", "error: #{e}")
|
43
|
+
rescue Errno::EACCES => e
|
44
|
+
@logger = old_logger if !old_logger.nil?
|
45
|
+
logging("error", "can not access log file (#{log_file_path})")
|
46
|
+
logging("error", "error: #{e}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def close()
|
51
|
+
@logger.close if !@logger.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def logging serverity, msg
|
55
|
+
serverity = fixup_level(serverity)
|
56
|
+
if !@logger.nil?
|
57
|
+
case serverity
|
58
|
+
when "debug"
|
59
|
+
@logger.debug(msg)
|
60
|
+
when "info"
|
61
|
+
@logger.info(msg)
|
62
|
+
when "warn"
|
63
|
+
@logger.warn(msg)
|
64
|
+
when "error"
|
65
|
+
@logger.error(msg)
|
66
|
+
when "fatal"
|
67
|
+
@logger.fatal(msg)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@syslog_wrapper.logging("log", serverity, msg) if @@log_level_map[serverity] >= @serverity
|
71
|
+
puts "[#{serverity}] #{msg}" if @foreground && @@log_level_map[serverity] >= @serverity
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Pmux
|
4
|
+
module Gateway
|
5
|
+
class PmuxHandler < EventMachine::Connection
|
6
|
+
# pmuxを実行した結果を処理するクラス
|
7
|
+
def initialize(*args)
|
8
|
+
@logger = LoggerWrapper.instance()
|
9
|
+
@cc = args.shift()
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_init
|
13
|
+
@logger.logging("debug", "post init pmux handler")
|
14
|
+
# エラー検出モードでない場合はpmuxを実行した段階でheaderを送信する
|
15
|
+
if !@cc.detect_error
|
16
|
+
@cc.response.chunks ||= []
|
17
|
+
@cc.response.content_type("application/octet-stream")
|
18
|
+
@cc.response.status = 200
|
19
|
+
@cc.response.send_headers
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive_data(data)
|
24
|
+
@logger.logging("debug", "received data from stdout")
|
25
|
+
if @cc.detect_error
|
26
|
+
# エラー検出モードの場合は長さが規定値を超えるまでバッファする
|
27
|
+
# 超えた場合はpmuxの実行を終了する
|
28
|
+
if @cc.stdout_data.length > $config["max_content_size"]
|
29
|
+
close_connection()
|
30
|
+
@cc.content_too_big = true
|
31
|
+
else
|
32
|
+
@cc.append_stdout_data(data)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
# エラー検出モードでない場合はchunkデータとしてレスポンスを返す
|
36
|
+
if data.length > 0
|
37
|
+
@cc.response.chunk(data)
|
38
|
+
@cc.response.send_body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def receive_stderr data
|
44
|
+
@logger.logging("debug", "received data from stderr")
|
45
|
+
@cc.append_stderr_data(data)
|
46
|
+
end
|
47
|
+
|
48
|
+
def unbind
|
49
|
+
# クライアントとの接続が切れている場合は何もできない
|
50
|
+
if @cc.force_pmux_terminated
|
51
|
+
@logger.logging("info", "peer: #{@cc.peername}, command: #{@cc.command}, response: force termination")
|
52
|
+
return
|
53
|
+
end
|
54
|
+
retcode = get_status.exitstatus
|
55
|
+
if @cc.detect_error
|
56
|
+
# エラー検出モードの場合はバッフしていた情報を返す
|
57
|
+
# ただし、コンテントが規定値を超えていた場合はその旨を返し、
|
58
|
+
# pmux実行のステータスコードが0でない場合はその値とstderrを返す
|
59
|
+
@cc.response.content_type("application/octet-stream")
|
60
|
+
if @cc.content_too_big
|
61
|
+
@cc.response.status = 500
|
62
|
+
@cc.response.content = "#{retcode}\r\ntoo big response data size with detect error (#{@cc.stdout_data.length} byte)"
|
63
|
+
elsif retcode == 0
|
64
|
+
@cc.response.status = 200
|
65
|
+
@cc.response.content = @cc.stdout_data
|
66
|
+
else
|
67
|
+
@cc.response.status = 500
|
68
|
+
@cc.response.content = "#{retcode}\r\n#{@cc.stderr_data}"
|
69
|
+
end
|
70
|
+
@cc.response.send_response
|
71
|
+
else
|
72
|
+
# エラー検出モードでない場合はchunkの終端を送る
|
73
|
+
# chunkの場合はsend_responseの処理を分離しているので
|
74
|
+
# (eventmachine_httpserverのコード参照)
|
75
|
+
# keepaliveの処理と思われる部分をそのまま持ってきた
|
76
|
+
@cc.response.send_trailer
|
77
|
+
@cc.response.close_connection_after_writing unless (
|
78
|
+
@cc.response.keep_connection_open and (@cc.response.status || "200 OK") == "200 OK")
|
79
|
+
end
|
80
|
+
@logger.logging("info", "(response) peer: #{@cc.peername}, pid: #{@cc.pid}, command: #{@cc.command}, response: #{@cc.response.status}")
|
81
|
+
@cc.pmux_terminated = true
|
82
|
+
@logger.logging("debug", "pmux terminated ")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|