pmux-gw 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|