Python Flask 作为 Windows 服务

Python Flask as Windows Service

我正在尝试将 Flask 应用作为 Windows 中的一项服务提供给 运行。我已经尝试按照建议实施解决方案 here and here 但没有成功。

我有一个只有两个文件的简单文件夹:

Project
 |
 +-- myapp.py   
 +-- win32_service.py

里面 myapp.py 是一个简单的 Flask 应用程序:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

和服务骨架win32_service.py:

import win32serviceutil
import win32service
import win32event
import win32evtlogutil
import servicemanager
import socket
import time
import logging
import os
import sys

sys.path.append(os.path.dirname(__name__))

from myapp import app

logging.basicConfig(
    filename = r'c:\tmp\flask-service.log',
    level = logging.DEBUG, 
    format = '[flaskapp] %(levelname)-7.7s %(message)s'
)

class HelloFlaskSvc (win32serviceutil.ServiceFramework):
    _svc_name_ = "FlaskApp"
    _svc_display_name_ = "FlaskApp Service"

    def __init__(self, *args):
        win32serviceutil.ServiceFramework.__init__(self, *args)
        self.hWaitStop = win32event.CreateEvent(None,0,0,None)
        socket.setdefaulttimeout(5)
        self.stop_requested = False

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
        logging.info('Stopped service ...')
        self.stop_requested = True

    def SvcDoRun(self):
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTED,
            (self._svc_name_,'')
        )

        self.main()

    def main(self):
        app.run(host="127.0.0.1", port=8000)

if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(HelloFlaskSvc)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(HelloFlaskSvc)

然后我使用以下命令通过 pyinstaller 将其编译为 exe 文件:

pyinstaller --onefile --hidden-import win32timezone win32_service.py

我编译成功 exe。然后我继续注册服务(以管理员权限打开 cmd):

>>> win32_service.exe install
> Installing service FlaskApp
> Service installed

然后我尝试启动它:

>>> win32_service.exe start
> Starting service FlaskApp

但随后什么也没有发生(没有错误)。此外,如果我尝试从任务管理器启动它,它会将状态更改为 Starting,然后更改为 Stopped

这些是安装在 virtualenv 中的模块:

altgraph==0.16.1
Click==7.0
Flask==1.0.2
future==0.17.1
itsdangerous==1.1.0
Jinja2==2.10.1
macholib==1.11
MarkupSafe==1.1.1
pefile==2018.8.8
PyInstaller==3.4
pyodbc==4.0.26
pywin32==224
pywin32-ctypes==0.2.0
Werkzeug==0.15.2

系统规格:

Python - 3.6.5 
OS     - Windows 10

我在这里缺少什么?任何帮助表示赞赏。

编辑

Windows EventViewer 显示错误:

The description for Event ID 3 from source FlaskApp cannot be found. Either the component that raises this event is not installed on your local computer or the installation is corrupted. You can install or repair the component on the local computer.

If the event originated on another computer, the display information had to be saved with the event.

The following information was included with the event: 

Traceback (most recent call last):
  File "lib\site-packages\win32\lib\win32serviceutil.py", line 839, in SvcRun
  File "win32_service.py", line 47, in SvcDoRun
  File "win32_service.py", line 50, in main
  File "lib\site-packages\flask\app.py", line 938, in run
  File "lib\site-packages\flask\cli.py", line 629, in show_server_banner
  File "lib\site-packages\click\utils.py", line 260, in echo
SystemError: <built-in method replace of str object at 0x000001E36AD465D0> returned a result with an error set

编辑 2

如果我使用单个 spec 文件,隐藏导入找不到某些模块(这是 pyinstaller:

的输出
4972 INFO: Analyzing hidden import 'ClickFlask'
4973 ERROR: Hidden import 'ClickFlask' not found
4974 INFO: Analyzing hidden import 'future'
4981 INFO: Analyzing hidden import 'itsdangerous'
5029 INFO: Analyzing hidden import 'Jinja2'
5030 ERROR: Hidden import 'Jinja2' not found
5030 INFO: Analyzing hidden import 'MarkupSafe'
5032 ERROR: Hidden import 'MarkupSafe' not found
5033 INFO: Analyzing hidden import 'pyodbc'
5034 INFO: Analyzing hidden import 'pywin32'
5035 ERROR: Hidden import 'pywin32' not found
5035 INFO: Analyzing hidden import 'pywin32-ctypes'
5036 ERROR: Hidden import 'pywin32-ctypes' not found

会不会跟这个有关?为什么有的模块能找到,有的却找不到?我正在使用 virtualenv。

根据 Reddit post 的说法,将所有库添加到 hiddenimports 应该可以解决您的问题,我自己试过了,确实有效!

因此,在您的项目目录中创建一个名为 win32_service.spec 的文件,内容如下

# -*- mode: python -*-

block_cipher = None


a = Analysis(['win32_service.py'],
             pathex=['C:\Users\Win7\Desktop\FaaS'],
             binaries=[],
             datas=[],
             hiddenimports=['win32timezone',
                            'altgraph',
                            'Click'
                            'Flask',
                            'future',
                            'itsdangerous',
                            'Jinja2',
                            'macholib',
                            'MarkupSafe',
                            'pefile',
                            'PyInstaller',
                            'pyodbc',
                            'pywin32',
                            'pywin32-ctypes',
                            'Werkzeug',],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='win32_service',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          runtime_tmpdir=None,
          console=True )

别忘了更改 pathex 变量

然后使用以下命令代替 pyinstaller --onefile --hidden-import win32timezone win32_service.pypyinstaller --onefile win32_service.spec

我进一步研究了 pyinstaller github 存储库并解决了这个问题。

似乎pyinstallerWindows 10有些冲突,但this issue是我的问题的关键。尽管产生错误的模块不一样。

我设法通过在 lib\site-packages\click\utils.pyline 260 处添加 SystemError 异常来解决它 echo 函数。

所以我改变这个:

if message:
   file.write(message)

为此:

if message:
    try:
        file.write(message)
    except SystemError:
        pass

使用以下方法重建 exe:

pyinstaller --onefile --hidden-import win32timezone win32_service.py

安装了服务,然后就可以正常启动了。

这个问题是由于 pywin32,安装 pywin32 可执行文件,避免使用 pip 安装它,同时确保在使用 pyinstaller 编译它时使用 --hidden-import=win32timezone 标志。

还有 运行 来自 CMD(管理员)的服务

这是循序渐进的新手指南https://github.com/PushpenderIndia/PythonWindowsService

也在这里发布相同的解决方案。

创建 Python Windows 服务的步骤

(1) 将这些代码复制粘贴到 Python 文件(例如 server.py)

import servicemanager
import sys
import win32serviceutil
from mainserver import FlaskServer   # Import your code, I've written a module called mainserver which contains FlaskServer Code using OOPs
import threading
import concurrent.futures
import time

class workingthread(threading.Thread):
    def __init__(self, quitEvent):
        self.quitEvent = quitEvent
        self.waitTime = 1
        threading.Thread.__init__(self)

    def run(self):
        try:
            # Running start_flask() function on different thread, so that it doesn't blocks the code
            executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
            executor.submit(self.start_flask)
        except:
            pass

        # Following Lines are written so that, the program doesn't get quit
        # Will Run a Endless While Loop till Stop signal is not received from Windows Service API
        while not self.quitEvent.isSet():  # If stop signal is triggered, exit
            time.sleep(1)

    def start_flask(self):
        # This Function contains the actual logic, of windows service
        # This is case, we are running our flaskserver
        test = FlaskServer()
        test.start()

class FlaskService(win32serviceutil.ServiceFramework):
    _svc_name_ = "AA Testing"
    _svc_display_name_ = "AAA Testing"
    _svc_description_ = "This is my service"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = threading.Event()
        self.thread = workingthread(self.hWaitStop)

    def SvcStop(self):
        self.hWaitStop.set()

    def SvcDoRun(self):
        self.thread.start()
        self.hWaitStop.wait()
        self.thread.join()


if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(FlaskService)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(FlaskService)

(2) 安装最新的 pywin32.exe

  • 注意:如果您使用 pip 安装 pywin32,那么它将无法正确安装,因此会显示一些错误,
  • 访问https://github.com/mhammond/pywin32/releases
  • 并下载最新的exe,如果你使用的是Python 32位,那么下载pywin32-302.win32-py3.x.exe
  • 如果使用 Python 64 位,则下载 pywin32-302.win-amd64-py3.x.exe

(3) 使用 pyinstaller

编译你的 server.py
  • 正在编译服务可执行文件
C:\Users\Pushpender\Desktop> python -m pip install servicemanager
C:\Users\Pushpender\Desktop> pyinstaller --onefile server.py --hidden-import=win32timezone --clean --uac-admin
  • 正在安装和 运行ning 服务可执行文件(运行 CMD 作为管理员)
C:\WINDOWS\system32>cd C:\Users\Pushpender\Desktop>
C:\WINDOWS\system32>d:
C:\Users\Pushpender\Desktop>server.exe --startup=auto install       # Installing service with startup == Automatic    
C:\Users\Pushpender\Desktop>server.exe start    # For starting service (You can start from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>server.exe stop     # For stopping service (You can stop from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>server.exe remove   # For removing installed service

(4) 可以直接运行 server.py不用编译(运行 CMD as Administrator)

C:\WINDOWS\system32>cd C:\Users\Pushpender\Desktop>
C:\WINDOWS\system32>d:
C:\Users\Pushpender\Desktop>python server.py --startup=auto install   # Installing service with startup == Automatic   
C:\Users\Pushpender\Desktop>python server.py start     # For starting service (You can start from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>python server.py stop      # For stopping service (You can stop from Windows Service or From Task Manager)
C:\Users\Pushpender\Desktop>python server.py remove    # For removing installed service

注意:

  • 您可以调整上面的代码,例如,您可以更改以下内容

      _svc_display_name_ = "AAA Testing"
      _svc_description_ = "This is my service"
    
  • 您还可以更改 FlaskService 的 类 名称,workingthread

  • 请更改代码中的这一行 from mainserver import FlaskServer

  • 您可以将启动类型更改为很多东西,键入 server.exe --help 以了解有关设置的更多信息

  • 如果要设置StartUp=Manaull,那么在安装服务时不要使用--startup=auto

  • 如果要设置StartUp=Automatic (Delayed),那么在安装服务的时候使用--startup=delayed

  • install 参数前使用 --startup 参数

  • 如果您在代码中使用了任何类型的路径,例如“/logging/logs.txt”,那么确保在您的代码中使用 full/absolute 路径,例如“C:/logging/logs.txt”。因为 windows 将从任何其他路径调用您的服务

  • 使用此命令 运行 您的服务处于调试模式:server.exe debug

(4) 将 Windows 服务器与 Inno Setup Builder 集成

  • 如果您想创建一个安装程序,它将在安装过程中安装您的服务并在卸载时将其删除,那么
  • 将这些代码块添加到您的 script.iss
[Run]
Filename: "{app}\{#MyAppExeName}"; StatusMsg: "Installing Windows Service ... "; Parameters: "--startup=delayed install";  Flags: runhidden waituntilterminated  
Filename: "{app}\{#MyAppExeName}"; StatusMsg: "Running Windows Service ... "; Parameters: "start";  Flags: runhidden waituntilterminated

[UninstallRun]
Filename: "{app}\{#MyAppExeName}"; Parameters: "remove";  Flags: runhidden