Python으로 Windows Service 제작해보기

지난 주에 breakpad로 간단한 크래시 리포팅 툴을 작성했다. 이 리포트를 받는 툴을 python으로 작성했는데, 이걸 서비스로 띄우기 위해 어제 했던 삽질을 간략히(?) 정리 해보는 차원에서 글 하나.

우선 아주 간단한 웹 서버를 예로 쓰겠다. localhost:8000에서 HTTP GET으로 요청이 들어오면, 이 GET 경로를 그대로 text/plain으로 보내주는 서버다. 예를 들자면 http://127.0.0.1/path;param?var=value#frag 요청에 대해서 /path;param?var=value 가 웹 페이지에 찍힌다.

import BaseHTTPServer
from SocketServer import ThreadingTCPServer

class EchoHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  def do_GET(self):
    self.send_response(200)
    self.send_header(‘Content-Type', 'text/plaincharset=UTF-8′)
    self.end_headers()
    self.wfile.write(self.path)

class WebServer(ThreadingTCPServerBaseHTTPServer.HTTPServer):
  pass

if __name__ == "__main__":
  httpd = WebServer((‘127.0.0.1', 8000), EchoHandler)
  httpd.serve_forever()

이 서버를 pywin32를 써서, Windows NT Service로 만들었다. 이건 처음 해보는 거라 아주 제대로 삽질했다. 일단 코드 자체는 아래처럼 생겼다. 대략 코드를 보고 동작을 이해할 수 있다 – 다만 개인적으로 이해 안가는 건, SERVICE_STOPPED를 통보하면 없는 HANDLE이라는 이벤트 로그가 남더라. 그냥 놔두면 잘 된다.

import win32servicewin32serviceutilwin32eventservicemanager
import httpd

class HttpdService(win32serviceutil.ServiceFramework):
  _svc_name_ = 'httpd'
  _svc_display_name_ = 'Simple Web Server'

  def __init__(selfargs):
    win32serviceutil.ServiceFramework.__init__(selfargs)
    self.haltEvent = win32event.CreateEvent(None, , , None)

  def SvcStop(self):
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    self.server.shutdown()
    win32event.SetEvent(self.haltEvent)

  def SvcDoRun(self):
    self.server = httpd.WebServer((‘127.0.0.1', 8000), httpd.EchoHandler)
    servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
        servicemanager.PYS_SERVICE_STARTED, (self._svc_name_"))
    self.server.serve_forever()
    win32event.WaitForSingleObject(self.haltEvent, win32event.INFINITE)

if __name__ == '__main__':
  win32serviceutil.HandleCommandLine(HttpdService)
 

그리고 나서, py2exe를 써서 다시 배포 가능한 binary로 만들었다.1 그리고 실제 서버로 가져가서 배포하고, 띄워서 이 삽질은 종료. py2exe로 단순 바이너리는 몇 번 만들어봐서 쉽게 될 줄 알았는데, 서비스의 경우 빌드 방법이 좀 다르더라;

이건 python 설치 디렉터리의 lib/site-packages/py2exe/samples/advanced/setup.py를 참고해서 아래처럼 하니까 잘 되더라.

# code from libsite-packagespy2exesamplesadvancedsetup.py
from distutils.core import setup
import py2exe

class Target:
  def __init__(selfkw):
    self.__dict__.update(kw)
    self.version = "0.1.0.0″
    self.company_name = "UPnL"
    self.copyright = "Copyright (C) 2010 rein@????.org"
    self.name = "Simple Echo WebServer"

target = Target(
  description = "A sample web server service",
  modules=[‘httpdservice'],
  cmdline_style=‘pywin32',
)

setup(service = [target])
  회사에서 한 번 하고 나니, 집에 와서 다시 짜는 데는 시간 얼마 안 걸리더라; 앞으로는 Windows 서비스로 띄워야 해도, 간단한 거라면 그냥 python으로 작성하게 될 듯 하다


  1. 물론 한 움큼의 dll, pyd(=dll), .zip 파일이 따라 붙는다. ↩︎