164 lines
5.6 KiB
Python
164 lines
5.6 KiB
Python
# =========================================================
|
||
# ======= Web服务器 ========
|
||
# ======= http接口可复用于跨进程命令行、防止多开等方面 ========
|
||
# =========================================================
|
||
|
||
from PySide2.QtCore import QThreadPool, QRunnable
|
||
from wsgiref.simple_server import make_server, WSGIServer
|
||
|
||
from umi_log import logger
|
||
from ..utils import pre_configs
|
||
from ..utils.call_func import CallFunc
|
||
from .bottle import Bottle, ServerAdapter, request, HTTPResponse, response, BaseRequest
|
||
from .cmd_server import CmdServer
|
||
from . import ocr_server
|
||
from . import qrcode_server
|
||
from . import doc_server
|
||
|
||
BaseRequest.MEMFILE_MAX = 104857600 # 设置单次请求大小上限: 100 MB
|
||
|
||
UmiWeb = Bottle()
|
||
Host = "127.0.0.1" # 由qml设置
|
||
|
||
|
||
# 允许跨域
|
||
@UmiWeb.hook("before_request")
|
||
def _validate_before():
|
||
re_method = request.environ.get("REQUEST_METHOD")
|
||
hacrm = request.environ.get("HTTP_ACCESS_CONTROL_REQUEST_METHOD")
|
||
if re_method == "OPTIONS" and hacrm:
|
||
request.environ["REQUEST_METHOD"] = hacrm
|
||
|
||
|
||
@UmiWeb.hook("after_request")
|
||
def _validate_after():
|
||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||
response.headers["Access-Control-Allow-Headers"] = "*"
|
||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
|
||
|
||
|
||
# ============================== 基础路由 ==============================
|
||
|
||
|
||
@UmiWeb.route("/")
|
||
@UmiWeb.route("/umiocr")
|
||
def _umiocr():
|
||
from umi_about import UmiAbout # 项目信息
|
||
|
||
return UmiAbout["fullname"]
|
||
|
||
|
||
# 跨进程接收命令行参数
|
||
@UmiWeb.route("/argv", method="POST")
|
||
def _argv():
|
||
addr = request.environ.get("REMOTE_ADDR")
|
||
if addr == "127.0.0.1":
|
||
data = request.json
|
||
res = CmdServer.execute(data)
|
||
return res
|
||
else:
|
||
msg = "Unauthorized access. Only local requests are allowed.\n此接口只允许本机访问。"
|
||
return HTTPResponse(msg, status=401)
|
||
|
||
|
||
ocr_server.init(UmiWeb)
|
||
qrcode_server.init(UmiWeb)
|
||
doc_server.init(UmiWeb)
|
||
|
||
# =============== 自定义服务器适配器,方便控制服务终止 ==============================
|
||
QmlCallback = None # qml回调函数
|
||
|
||
|
||
class _WSGIRefServer(ServerAdapter):
|
||
# https://stackoverflow.com/questions/11282218/bottle-web-framework-how-to-stop
|
||
|
||
class CustomWSGIServer(WSGIServer): # 定制服务器
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
self.activeConnections = set() # 当前活跃连接
|
||
|
||
def process_request(self, request, client_address):
|
||
# 记录活跃的连接
|
||
self.activeConnections.add(request)
|
||
super().process_request(request, client_address)
|
||
|
||
def close_all_request(self): # 关闭所有活跃的连接
|
||
import socket
|
||
|
||
for request in self.activeConnections:
|
||
try:
|
||
request.shutdown(socket.SHUT_RDWR)
|
||
request.close()
|
||
logger.info(f"强制关闭连接。 request: {request}")
|
||
except OSError:
|
||
pass
|
||
except Exception:
|
||
logger.error(
|
||
f"强制连接异常。 request: {request}",
|
||
exc_info=True,
|
||
stack_info=True,
|
||
)
|
||
|
||
def run(self, handler):
|
||
import atexit # 退出处理
|
||
|
||
atexit.register(self.stop) # 注册程序终止时停止线程
|
||
self.port = pre_configs.getValue("server_port") # 提取记录的端口号
|
||
self.host = Host
|
||
# 找到一个可用的端口号
|
||
while True:
|
||
try:
|
||
self.server = make_server(
|
||
self.host,
|
||
self.port,
|
||
handler,
|
||
server_class=self.CustomWSGIServer,
|
||
**self.options,
|
||
)
|
||
break
|
||
except OSError: # 当前端口号已占用,测试下一位端口号
|
||
logger.warning(f"服务器端口号 {self.port} 已被占用")
|
||
self.port += 1
|
||
if self.port > 65535:
|
||
self.port = 1024
|
||
pre_configs.setValue("server_port", self.port) # 写入记录
|
||
|
||
logger.info(f"Listening on http://{self.host}:{self.port}")
|
||
print(f"Listening on http://{self.host}:{self.port}")
|
||
CallFunc.now(QmlCallback, self.port) # 在主线程中调用回调函数,告知实际端口号
|
||
self.server.serve_forever()
|
||
|
||
def stop(self): # 服务终止
|
||
# self.server.server_close() # 备选方案,但会导致 bad fd 异常
|
||
logger.debug("WEB服务器准备关闭!")
|
||
self.server.close_all_request() # 强制关闭客户端连接
|
||
self.server.shutdown() # 关闭服务器
|
||
logger.info("WEB服务器已关闭!")
|
||
|
||
|
||
# ============================== 线程类 ==============================
|
||
class _WorkerClass(QRunnable):
|
||
def run(self):
|
||
self._server = _WSGIRefServer()
|
||
UmiWeb.run(server=self._server)
|
||
|
||
|
||
_Worker = _WorkerClass()
|
||
|
||
|
||
# ============================== 控制接口 ==============================
|
||
|
||
|
||
# 启动web服务。传入qml对象,回调函数名,主机地址
|
||
def runUmiWeb(qmlObj, callback, host):
|
||
global QmlCallback, Host
|
||
Host = host
|
||
QmlCallback = getattr(qmlObj, callback, None) # 提取qml回调函数
|
||
threadPool = QThreadPool.globalInstance() # 获取全局线程池
|
||
threadPool.start(_Worker) # 启动服务器线程
|
||
|
||
|
||
# 切换端口号(下次启动生效)
|
||
def setPort(port):
|
||
pre_configs.setValue("server_port", port)
|