在做基於B/S應用中,經常有需要後臺運行任務的需求,最簡單比如發送郵件。在一些如防火牆,WAF等項目中,前臺只是爲了展示內容與各種參數配置,後臺守護進程纔是重頭戲。所以在防火牆配置頁面中可能會經常看到調用cgi,但真正做事的一般並不是cgi,比如說執行關機命令,他們的邏輯如下:
(ps:上圖所說的前臺界面包含通常web開發中的後端,不然也沒有socket一說)
爲什麼要這麼設計
如何實現
class MgrService(win32serviceutil.ServiceFramework):
"""
Usage: 'python topmgr.py install|remove|start|stop|restart'
"""
#服務名
_svc_name_ = "Mgr"
#服務顯示名稱
_svc_display_name_ = "Daemon Mgr"
#服務描述
_svc_description_ = "Daemon Mgr"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
def SvcDoRun(self):
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
INFO("mgr startting...")
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
self.start()
# 等待服務被停止
INFO("mgr waitting...")
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
INFO("mgr end")
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
INFO("mgr stopping...")
self.stop()
INFO("mgr stopped")
# 設置事件
win32event.SetEvent(self.hWaitStop)
self.ReportServiceStatus(win32service.SERVICE_STOPPED)
def start(self): pass
def stop(self): pass
很簡單,這樣就實現了windows中的服務,也就是說脫離終端,運行於後臺。INFO等函數只是簡單的記錄作用,可直接忽略。class Engine(MgrService):
rbufsize = -1
wbufsize = 0
def start(self):
INFO('wait connection')
self.server = StreamServer((HOST, PORT), self.msg_handle)
self.server.serve_forever()
def msg_handle(self,socket,address):
try:
rfile = socket.makefile('rb', self.rbufsize)
wfile = socket.makefile('wb', self.wbufsize)
headers = Message(rfile).dict
INFO('get a connection from:%s,headers:%s' % (str(address), headers))
if 'module' in headers and headers['module'] in MODULES:
MODULES[headers['module']].handle(wfile, headers)
except Exception:
ERROR('msg_handle exception,please check')
def stop(self):
if hasattr(self, server):
self.server.stop()
當有新連接到來,由msg_handle處理,首先讀取發送來的消息,消息格式使用了最簡單的http的格式,即(鍵名:鍵值)的格式,你要問我爲什麼採用這個格式,哈哈,格式簡單,python有現成的庫解析。上面代碼的那個MODULES是個全局變量,當你添加一個模塊的時候需要註冊到MODULES中,我提供了module_register方法。
MODULES = { # module: handle module class
}
def module_register(module_name, handle_class):
if module_name in MODULES:
WARN('duplicate module_name:' + module_name)
else:
MODULES[module_name] = handle_class
class Module(object):
SECRE_KEY = "YI-LUO-KEHAN"
MODULE_NAME = "BASE_MODULE"
PREFIX = "do_" # method prefix
def __init__(self, wfile, headers):
self.wfile = wfile
self.headers = headers
def __getattr__(self, name):
try:
return self.headers[name]
except Exception:
ERROR("%s has no attr:%s,please check" %(self.MODULE_NAME, name))
@classmethod
def handle(cls, wfile, headers):
module_obj = cls(wfile, headers)
module_obj.schedule_default()
def verify(self):
if hmac.new(self.SECRE_KEY, self.MODULE_NAME).hexdigest() == self.signature:
return True
else:
WARN("client verify failed,signature:%s" % str(self.signature))
def schedule_default(self):
err_code = 0
if self.verify() and self.action:
func_name = self.PREFIX + self.action
try:
getattr(self, func_name)()
except AttributeError:
err_code = 1
ERROR("%s has no method:%s" %(self.MODULE_NAME, func_name))
except Exception:
err_code = 2
ERROR("module:%s,method:%s,exception" % (self.MODULE_NAME, func_name))
else:
err_code = 3
if err_code:
self.send_error({'err_code':err_code})
def send_success(self, msg=''):
data = {'success':True,'msg':msg}
self.wfile.write(json.dumps(data))
def send_error(self, msg=''):
data = {'success':False,'msg':msg}
self.wfile.write(json.dumps(data))
在基類模塊中我們提供了默認的處理流程,即根據消息中action,調用do_action方法,並提供了一個簡單但很有效的認證方法,通過消息的signature字段,可能有些簡陋,但沒關係,你可以定義自己的認證方法。
TASK = {} # task_id: pid
class ScanModule(Module):
MODULE_NAME = "SCAN_MODULE"
def do_start(self):
self.send_success('start ok')
DEBUG('------------task start------------')
task_ids = [int(task_id) for task_id in self.task_ids.split(',') if int(task_id) not in TASK]
for task_id in task_ids:
try:
cmd = 'python scan.py -t %s' % task_id
DEBUG(cmd)
self.sub = Popen(cmd, shell=True, cwd=CWD)
pid = int(self.sub.pid)
TASK[task_id] = pid
INFO('%s start a new task,task_id:%s,pid:%s' %(self.MODULE_NAME, task_id, pid))
except Exception:
ERROR('%s start a new task,task_id:%s failed' % (self.MODULE_NAME, task_id))
def do_stop(self):
self.send_success('stop ok')
DEBUG('------------task stop------------')
task_ids = [int(task_id) for task_id in self.task_ids.split(',') if int(task_id) in TASK]
for task_id in task_ids:
pid = TASK.pop(task_id)
try:
INFO('%s stop a new task,task_id:%s,pid:%s' %(self.MODULE_NAME, task_id, pid))
call(['taskkill', '/F', '/T', '/PID', str(pid)])
except Exception:
ERROR('%s taskkill a task failed,task_id:%s,pid:%s' %(self.MODULE_NAME, task_id, pid))
module_register(ScanModule.MODULE_NAME, ScanModule)
#!/usr/bin/env python
#-*-encoding:UTF-8-*-
import hmac
import gevent
from gevent import monkey
monkey.patch_socket()
addr = ('localhost', 6667)
def send_request(module_name,request_headers):
SECRE_KEY = "YI-LUO-KEHAN"
socket = gevent.socket.socket()
socket.connect(addr)
request_headers['module'] = module_name
request_headers['signature'] = hmac.new(SECRE_KEY, module_name).hexdigest()
h = ["%s:%s" %(k, v) for k,v in request_headers.iteritems()]
h.append('\n')
request = '\n'.join(h)
socket.send(request)
print socket.recv(8192)
socket.close()
if __name__ =="__main__":
import sys
if sys.argv[1] == 'start':
send_request('SCAN_MODULE',{'action':'start','task_ids':'1'})
else:
send_request('SCAN_MODULE',{'action':'stop','task_ids':'1'})