Python Flask 作为 Windows 服务

Posted

技术标签:

【中文标题】Python Flask 作为 Windows 服务【英文标题】:Python Flask as Windows Service 【发布时间】:2019-09-04 17:25:49 【问题描述】:

我正在尝试让Flask 应用程序在 Windows 中作为服务运行。我已经尝试按照here 和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。

【问题讨论】:

它是否在 EventViewer 中记录事件?您在那里看到任何有用的信息吗? @amanb 感谢您指出这一点。是的,将使用 EventViewer 中的错误进行更新。 您如何将两个文件中的代码合并到一个 Python 脚本中,然后尝试看看它是否有效。稍后,它们可以分成单独的脚本。这个SO answer 可能会有所帮助。 不走运,如果我只使用一个 Python 脚本,我也会遇到同样的错误。 好的,我会在我的 Windows 机器上测试它并分享我的结果 【参考方案1】:

根据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

【讨论】:

对不起,我收到同样的错误File "lib\site-packages\click\utils.py", line 260, in echo,并且服务没有启动.. @drec4s 你试过win32_service.exe debug吗? 它给出了一个Fatal error detected: Failed to execute script win32_service @drec4s 我刚看到新的更新,是模块【参考方案2】:

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

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

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

所以我改变了这个:

if message:
   file.write(message)

到这里:

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

使用以下命令重建 exe:

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

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

【讨论】:

【参考方案3】:

这个问题是由于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 32bit,请下载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
安装和运行服务可执行文件(以管理员身份运行 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)

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"

您也可以更改FlaskServiceworkingthread的类名

请在您的代码中更改此行from mainserver import FlaskServer

您可以将启动类型更改为很多东西,输入server.exe --help以了解更多设置

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

如果要设置 StartUp= Automatic (Delayed),则在安装服务时使用--startup=delayed

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

如果您在代码中使用过任何类型的路径,例如“/logging/logs.txt”,请确保在代码中使用完整/绝对路径,例如“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

【讨论】:

以上是关于Python Flask 作为 Windows 服务的主要内容,如果未能解决你的问题,请参考以下文章

如何在windows下用flask做web开发

windows安装python flask虚拟环境

Windows下快速安装Flask的一次经历

Flask框架使用

通过 Python Flask 模拟 Windows 用户

如何更改工作的 python 程序,使其作为 python flask 程序运行