pmux-gw 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Gemfile +4 -0
  2. data/LICENSE.txt +22 -0
  3. data/Makefile +18 -0
  4. data/README.md +145 -0
  5. data/Rakefile +4 -0
  6. data/bin/pmux-gw +6 -0
  7. data/examples/password +4 -0
  8. data/examples/pmux-gw.conf +15 -0
  9. data/lib/pmux-gw/application.rb +226 -0
  10. data/lib/pmux-gw/client_context.rb +40 -0
  11. data/lib/pmux-gw/history.rb +149 -0
  12. data/lib/pmux-gw/http_handler.rb +454 -0
  13. data/lib/pmux-gw/logger_wrapper.rb +75 -0
  14. data/lib/pmux-gw/pmux_handler.rb +86 -0
  15. data/lib/pmux-gw/static/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  16. data/lib/pmux-gw/static/css/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  17. data/lib/pmux-gw/static/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  18. data/lib/pmux-gw/static/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  19. data/lib/pmux-gw/static/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  20. data/lib/pmux-gw/static/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  21. data/lib/pmux-gw/static/css/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  22. data/lib/pmux-gw/static/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  23. data/lib/pmux-gw/static/css/images/ui-icons_222222_256x240.png +0 -0
  24. data/lib/pmux-gw/static/css/images/ui-icons_2e83ff_256x240.png +0 -0
  25. data/lib/pmux-gw/static/css/images/ui-icons_454545_256x240.png +0 -0
  26. data/lib/pmux-gw/static/css/images/ui-icons_888888_256x240.png +0 -0
  27. data/lib/pmux-gw/static/css/images/ui-icons_cd0a0a_256x240.png +0 -0
  28. data/lib/pmux-gw/static/css/jquery-ui-1.9.2.custom.css +462 -0
  29. data/lib/pmux-gw/static/js/jquery-1.8.3.js +9472 -0
  30. data/lib/pmux-gw/static/js/jquery-ui-1.9.2.custom.js +14879 -0
  31. data/lib/pmux-gw/syslog_wrapper.rb +58 -0
  32. data/lib/pmux-gw/template/history.tmpl +101 -0
  33. data/lib/pmux-gw/version.rb +5 -0
  34. data/lib/pmux-gw.rb +12 -0
  35. data/pmux-gw.gemspec +26 -0
  36. data/rpm/Makefile +19 -0
  37. data/rpm/pmux-gw.spec +100 -0
  38. 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