37. 应用练习:第 17 版

此新版本带来了以下更新:
- 它将移植到 Apache / Windows 服务器上;
- 为实现此移植,第17版在其[impots / http-servers/ 12]文件夹中包含了所需的所有依赖项。需要提醒的是,之前的版本会从整个[python-flask-2020]项目中的不同文件夹中获取其依赖项;
37.1. 应用程序依赖项的迁移

需要提醒的是,应用程序依赖项的管理是在脚本 [syspath] 中进行的。在上一版本中,该脚本如下所示:
def configure(config: dict) -> dict:
import os
# 该文件的目录
script_dir = os.path.dirname(os.path.abspath(__file__))
# 根路径
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# 依赖项
absolute_dependencies = [
# 项目文件夹
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
f"{root_dir}/impots/v04/interfaces",
# AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
f"{root_dir}/impots/v04/services",
# ImpotsDaoWithAdminDataInDatabase
f"{root_dir}/impots/v05/services",
# AdminData, ImpôtsError, TaxPayer
f"{root_dir}/impots/v04/entities",
# 常量、区间
f"{root_dir}/impots/v05/entities",
# 记录器,SendAdminMail
f"{root_dir}/impots/http-servers/02/utilities",
# 主脚本文件夹
script_dir,
# 配置[database, layers, parameters, controllers, views]
f"{script_dir}/../configs",
# 控制器
f"{script_dir}/../controllers",
# 答案HTTP
f"{script_dir}/../responses",
# 视图模板
f"{script_dir}/../models_for_views",
]
# 设置 syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# 生成配置
return {
"root_dir": root_dir,
"script_dir": script_dir
}
必须重定位所有绝对路径依赖于第 8 行变量 [root_dir] 的依赖项,即第 13-26 行。
新版本中的脚本 [syspath] 将如下所示:
def configure(config: dict) -> dict:
import os
# 此文件的目录
script_dir = os.path.dirname(os.path.abspath(__file__))
# 依赖项
absolute_dependencies = [
# 应用程序实体
f"{script_dir}/../entities",
# 层 [dao]
f"{script_dir}/../layers/dao",
# 层 [métier]
f"{script_dir}/../layers/métier",
# 实用程序
f"{script_dir}/../utilities",
# 主脚本文件夹
script_dir,
# 配置[database, layers, parameters, controllers, views]
f"{script_dir}/../configs",
# 控制器
f"{script_dir}/../controllers",
# 答案HTTP
f"{script_dir}/../responses",
# 视图模板
f"{script_dir}/../models_for_views",
]
# 设置 syspath
import sys
# 为其添加项目的绝对依赖项
for directory in absolute_dependencies:
# 检查文件夹是否存在
existe = os.path.exists(directory) and os.path.isdir(directory)
if not existe:
# 向开发人员发出警告
raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
else:
# 将文件夹插入 syspath 的开头
sys.path.insert(0, directory)
# 生成配置
return {
"script_dir": script_dir,
}
- 第 8-27 行:所有依赖项现均相对于第 5 行的变量 [script_dir];
- 第 42-45 行:变量 [root_dir] 已从 syspath 配置中移除;
- 第 10 行:应用程序的实体位于 [entities] [1] 文件夹中;
- 第12行:[dao]层位于[layers/dao]和[2]文件夹中;
- 第 14 行:图层 [métier] 位于文件夹 [layers/métier] [2] 中;
- 第 16 行:[Logger, SendMail] 实用程序位于文件夹 [utilities] [3] 中;
- 第29-40行:计算应用程序的Python路径,且不导入模块[myutils];

37.2. 测试
至此,版本 17 应该可以运行。请进行验证。
37.3. 将 Python / Flask 应用程序移植到 Apache / Windows 服务器
37.3.1. 参考
为了将 Flask 应用移植到 Apache / Windows 环境,我不得不上网搜索。以下链接帮助我入门:[https://medium.com/@madumalt/flask-app-deployment-in-windows-apache-server-mod-wsgi-82e1cfeeb2ed];
我参考了该链接中的信息,但Apache服务器的配置除外。对于Apache服务器的配置,我采用了Laragon提供的示例配置。
37.3.2. 安装 Python 模块 mod_wsgi
我们开发的 Python / Flask 应用程序使用了随 Flask 一起提供的 WSGI(Web Server Gateway Interface)[werkzeug] 服务器。 该服务器的介绍请见|此处|。链接[https://www.fullstackpython.com/wsgi-servers.html]介绍了WSGI服务器的运行原理。 存在多种 |WSGI| 服务器。其中之一是运行在 WSGI 模式下的 Apache 服务器。本文采用此方案,是因为我们安装的 Laragon 自带 Apache 服务器。
为了让 Apache 服务器能够托管 Python 应用程序,我们需要安装 Python 模块 [mod_wsgi]。 该模块的安装较为复杂,因为安装过程中会进行 C++ 编译。要成功安装,需要 Microsoft C++ 编译器。一个简单的解决方案是安装最新版本的 Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/]。
如果除了 [mod_wsgi] 之外不需要使用 Visual Studio,可以将安装范围限定在 C++ 环境:

安装好 C++ 编译器后,可在终端中安装 [mod_wsgi] 模块:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>SET MOD_WSGI_APACHE_ROOTDIR=C:\MyPrograms\laragon\bin\apache\httpd-2.4.35-win64-VC15
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>pip install mod_wsgi
Collecting mod_wsgi
Using cached mod_wsgi-4.7.1.tar.gz (498 kB)
Using legacy setup.py install for mod-wsgi, since package 'wheel' is not installed.
Installing collected packages: mod-wsgi
Running setup.py install for mod-wsgi ... done
Successfully installed mod-wsgi-4.7.1
- 第 1 行:设置环境变量 [MOD_WSGI_APACHE_ROOTDIR] 的值。该值即文件系统中 Apache 服务器的路径。此处路径为 [<laragon>\bin\apache\httpd-2.4.35-win64-VC15],其中 <laragon> 是 Laragon 的安装目录。 您可以通过多种方式获取该路径。以下是通过 Laragon 其中一个选项获取的示例:

在 [1-3] 中,文件 [httpd.conf] 是 Apache 服务器的主配置文件。随后,该文件将在文本编辑器(下文为 Notepad++)中打开:

在 [2] 中,Apache 的安装目录位于字符串 [conf\httpd.conf] 之前。
让我们回到[mod_wsgi]模块的安装:
- 第3-9行:安装[mod_wsgi]模块;
37.3.3. 配置 Laragon 的 Apache 服务器
我们将配置 Laragon 的 Apache 服务器。首先从其主配置文件 [httpd.conf] 开始:

我们将光标移至文件末尾 [httpd.conf]:
…
IncludeOptional "C:/MyPrograms/laragon/etc/apache2/alias/*.conf"
IncludeOptional "C:/MyPrograms/laragon/etc/apache2/sites-enabled/*.conf"
Include "C:/MyPrograms/laragon/etc/apache2/httpd-ssl.conf"
Include "C:/MyPrograms/laragon/etc/apache2/mod_php.conf"
# python mod_wsgi
LoadModule wsgi_module "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages/mod_wsgi/server/mod_wsgi.cp38-win_amd64.pyd"
- 第 8 行已添加到文件 [httpd.conf] 的末尾。它告诉 Apache 服务器,我们刚刚安装的 [mod_wsgi] 模块中的某个元素位于何处;
获取第8行路径的简便方法是在终端中执行以下命令:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>mod_wsgi-express module-config
LoadModule wsgi_module "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages/mod_wsgi/server/mod_wsgi.cp38-win_amd64.pyd"
WSGIPythonHome "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv"
某些文档指出需将第 2 行和第 3 行添加到 [httpd.conf] 文件末尾。但在我的情况下,上述第 3 行会导致错误(缺少 [encodings] 模块)。 因此,该行未被放入文件 [httpd.conf] 中。仅将第 2 行放入其中。Apache 配置文件中可用的 [mod_wsgi] 模块各项参数的含义,请参阅 |此处|。
接下来,我们将启用 Apache 服务器的 HTTPS 协议:

- 在 [1-4] 中,启用 Apache 服务器的 HTTPS 协议;
现在,我们可以使用 URL 和 [https://serveur/chemin];
要配置 Apache 以托管 Flask 应用程序,上文提到的链接使用了虚拟主机。Laragon 同样支持管理虚拟主机:

- 在 [1-3] 中,我们要求 Laragon 自动创建虚拟主机;
下一步是使用 Laragon 创建一个 Web 项目:
![]() |
![]() | ![]() |
- 在 [1-3] 中,创建一个空的 PHP 项目;
- 在 [4-8] 中, Laragon 创建了一个名为 [auto.projet-test.test] 的虚拟站点,该站点由文件 [auto.projet-test.test.conf]、[8] 配置,位于文件夹 [sites-enabled]、[7] 中。 该文件夹位于 [<laragon>\etc\apache2\sites-enabled] 路径下,其中 [laragon] 是 Laragon 的安装目录;
虽然这与当前的工作无关,但您可能出于好奇想看看我们刚刚创建的 [projet-test] 网站:

- 在 [1-5] 中,已创建了一个空项目。这是一个位于 [<laragon>/www] 文件夹中的 PHP 项目,其中 [laragon] 是 Laragon 的安装文件夹;
现在让我们查看 Laragon 在 [<laragon>\etc\apache2\sites-enabled] 文件夹中生成的 [auto.projet-test.test.conf] 文件:
define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"
<VirtualHost *:80>
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:443>
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/MyPrograms/laragon/etc/ssl/laragon.key
</VirtualHost>
- 第 1 行:所创建项目在文件系统中的根目录;
- 第 2 行:虚拟服务器的名称。该服务器的 URL 文件将采用 [http(s)://projet-test.test/chemin] 的格式;
- 第 4-12 行:虚拟站点的配置,端口为 80(第 4 行),协议为 HTTP;
- 第14-27行:配置虚拟站点的443端口(第14行)和HTTPS协议;
让我们来看看虚拟服务器是如何工作的。首先启动 Apache 服务器和 PHP:
随后使用浏览器访问 URL [http://projet-test.test/]:

- 在 [1] 中,即请求的 URL;
- 在 [2] 中,使用了协议 HTTP;
- 在 [3] 中,由于项目 [projet-test] 为空,因此获取到其文件夹索引(内容列表),索引为空;
现在请求受保护的 URL 文件 [https://projet-test.test/]:

- 在 [1-2] 中,我们得到与之前相同的响应,但协议为 HTTPS [1];
创建虚拟服务器 [projet-test.test] 后,在文件 [<windows>/system32/drivers/etc/hosts] 中生成了一条新条目:

- 第 23 行:名称为 [projet-test.test] 的地址 IP 为 127.0.0.1,即 [localhost](第 20 行)的地址,即本地机器。 因此,当在浏览器中输入 URL [http://projet-test.test/chemin] 时,请求会被发送到 127.0.0.1 的 80 端口。此时,本地机器(localhost)上的 Apache 服务器会进行响应。
有人可能会问,为什么当输入请求 [http://projet-test.test/] 时,Apache 服务器会使用文件 [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf] 中的配置:

要理解这一点,我们需要查看浏览器在发出此请求时向 Apache 服务器发送的内容。让我们使用 Postman 进行测试:

- 在 [1-3] 中,我们发送的请求为 HTTPS [1];
- 当请求为 [4] 时,Postman 提示未识别安全证书。 HTTPS协议在Web客户端(此处为Postman)与Apache服务器之间建立加密连接。该加密连接通过客户端与服务器之间交换的证书实现。由服务器发起建立加密连接的对话,向客户端发送安全证书。 要使该证书被客户端接受,它必须经过签名,即从有权签发安全证书的公司购买。当我们启用 Laragon 的 HTTPS 协议时,Laragon 会自行生成安全证书。这种证书被称为自签名证书。 大多数Web客户端在收到自签名证书时会发出警告。Postman在[4]中就是如此。此时,大多数Web客户端会建议禁用对服务器发送的安全证书的验证。Postman在[5]中也提供了这一选项;
我们点击链接 [5] 以禁用安全套接层 (SSL) 验证 SSL。 SSL / TSL(传输层安全协议)是一种安全协议,可在互联网上的两台机器之间建立安全的通信通道。这是 Apache 在此处使用的协议。响应如下:

我们收到的页面与使用传统浏览器时完全相同。现在让我们在 Postman 控制台(Ctrl-Alt-C)中查看客户端与服务器的对话:
- 第 6 行:HTTP [Host] 头部指定了 Web 客户端所访问的目标服务器名称。这就是虚拟服务器的原理。 在同一地址(此处为 127.0.0.1)下,一台 Web 服务器可以托管多个名称不同的网站。 HTTP [Host] 头部使客户端能够指定其要连接的服务器(此处为 127.0.0.1);
那么 Apache 会做什么呢?
启动时,Apache会读取[[<laragon>\etc\apache2\sites-enabled]:

每个配置文件都定义了一个虚拟服务器。例如在文件 [auto.projet-test.test.conf] 中,有以下这一行:
define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"
…
第 2 行定义了虚拟服务器 [projet-test.test]。文件 [auto.projet-test.test.conf] 即为该虚拟服务器的配置文件。 由于 Apache 服务器在启动时会读取 [<laragon>\etc\apache2\sites-enabled] 文件夹中的所有配置文件,因此它知道存在一个名为 [projet-test.test] 的虚拟服务器。因此,当它从 Postman 客户端收到 HTTPS 的请求时:
它会识别出该请求是发往虚拟服务器 [projet-test.test](第 6 行),且该服务器确实存在。随后,它将使用虚拟服务器 [projet-test.test] 的配置来响应 Postman 客户端。
37.4. 创建第一个 Apache 虚拟服务器
既然我们已经了解了虚拟服务器的用途以及如何配置它们,接下来我们将创建一个。它将用于运行一个安装在[Apache]文件夹中的Python Flask应用程序,该文件夹属于正在Apache服务器上部署的17版本:
我们已将第 |链接| 段中开发的应用程序——一个日期/时间网络服务——放入文件夹 [http-servers/12/apache/exemple] 中:

服务器 [date_time_server.py] 如下:
# 导入
import os
import sys
import time
# 我们需要将模块文件夹添加到 Python 路径中
# 以便移植到 Windows 版 Apache
sys.path.insert(0, "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages")
from flask import Flask, make_response, render_template
# Flask 应用程序
script_dir = os.path.dirname(os.path.abspath(__file__))
application = Flask(__name__, template_folder=f"{script_dir}")
# 首页 URL
@application.route('/')
def index():
# 向客户端发送时间
# time.localtime:自 1970 年 1 月 1 日以来的毫秒数
# time.strftime 用于格式化时间和日期
# 日期和时间显示格式
# d:2位数的日期
# m:2位数的月份
# y:2位数的年份
# H:小时 0,23
# M:分钟
# S:秒
# 当前日期/时间
time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
# 生成要发送给客户的文档
page = {"date_heure": time_of_day}
document = render_template("date_time_server.html", page=page)
print("document", type(document), document)
# 向客户发送响应 HTTP
response = make_response(document)
print("response", type(response), response)
return response
# 仅主线程
if __name__ == '__main__':
application.config.update(ENV="development", DEBUG=True)
application.run()
Flask 应用程序通过标识符 [application] 进行引用(第 14、43、44 行)。该名称是必填的。 如果使用其他标识符引用该 Flask 应用程序,应用程序将无法运行,并显示一条错误消息,指出无法找到所请求的 URL。该错误消息并未提供任何关于错误来源的提示。因此,必须对此点保持警惕。
第34行引用的HTML文件内容如下:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Date et heure du moment</title>
</head>
<body>
<b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>
- [date-time-server]将是托管该应用程序的虚拟主机。它将由文件[<laragon>\etc\apache2\sites-enabled\date-time-server.conf]进行配置(需注意该名称可自定义——Apache会读取[sites-enabled]目录下的所有文件以识别所托管的虚拟站点);
我们首先通过复制文件 [auto.projet-test.test.conf] 来获取该文件,然后对其进行修改。

修改后的 [date-time-server.conf] 文件内容如下:
# 应用程序的 Python-Flask 脚本文件夹
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"
# 该文件配置的网站名称
# 此处将命名为 date-time-server
# URL 将采用 http(s)://date-time-server/路径 的形式
define SITE "date-time-server"
# 将 IP 的地址 127.0.0.1 添加到 c:/windows/system32/drivers/etc/hosts 文件中,用于 SITE 网站
# URL HTTP
<VirtualHost *:80>
# 使用别名 / URL 将采用 http(s)://日期-时间-服务器/路径/... 的形式
WSGIScriptAlias / "${ROOT}/date_time_server.py"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL 通过 HTTPS 进行加密
<VirtualHost *:443>
# 使用别名 / URL 将采用 http(s)://日期-时间-服务器/路径/... 的形式
WSGIScriptAlias / "${ROOT}/date_time_server.py"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/myprograms/laragon/etc/ssl/laragon.key
</VirtualHost>
- 第 7 行:为该文件配置的虚拟服务器命名;
- 第 2 行:为第 14 行和第 27 行中使用的变量 [ROOT] 赋值;
- 第 14 行和第 27 行:指定当虚拟服务器收到请求时需执行的 Python 脚本路径。此处指定针对服务器 [date-time-server] 的请求由 Python 脚本 [date_time_server.py] 处理。 与文件 [auto.projet-test.test.conf] 的区别在于:前者配置的是 PHP 服务器,而后者配置的是 Python 服务器;
- 第 14 行和第 27 行:此处的 [WSGIScriptAlias /] 属性表明,[date-time-server] 服务器的根目录将是 [/]。 因此,该应用程序中的 URL 将呈现为 [http(s)://date-time-server/chemin] 的形式;
- 第 14 行和第 27 行,我们可以为应用程序指定另一个根目录,例如 [WSGIScriptAlias /show]。那么应用程序中的 URL 将变为 [http(s)://show/date-time-server/chemin];
我们还需要在文件 [<windows>/system32/drivers/etc/hosts] 中添加一行:
在第 25 行添加该内容,将地址 IP [127.0.0.1] 分配给虚拟服务器 [date-time-server]。
让我们验证一下。启动 Apache 服务器:

随后,我们使用浏览器访问 URL [https://date-time-server]:

- 在 [1] 中,显示请求的 URL;
- 在 [3] 中,显示的是服务器的响应;
- 在 [2] 中,浏览器提示 HTTPS 连接不安全,因为它检测到 Apache 服务器发送的证书是自签名的;
现在在文件 [date-time-server.conf] 中,我们在第 14 行和第 27 行添加别名:
WSGIScriptAlias /show-date-time "${ROOT}/date_time_server.py"
Apache 服务器不会立即应用此修改。需要重新加载服务器:

随后请求 URL [https://date-time-server/show-date-time]。此时服务器的响应如下:

37.5. 将税款计算应用程序移植到 Apache / Windows 环境
文件 [apache] 和 [2] 最初是通过复制文件 [main] 生成的。 为了确保从 [1] 复制到 [2] 的脚本 [syspath.py] 的路径仍然有效,这两个文件必须位于同一层级。 为避免干扰正在运行的 [impots / http-servers/ 12] 应用程序,我们将由 Apache 服务器执行的配置放入 [apache] 中;

- [2]中的[config]与[1]中的[config]相同;
- [2] 中的 [syspath] 与 [1] 中的 [syspath] 相同;
- [2]生成的[main_withmysql]文件,是[1]生成的[main]文件,并进行了以下修改:
主脚本 [main] 接收一个名为 [mysql / pgres] 的参数,该参数指定应使用哪个 SGBD。 脚本 [main_withmysql] 使用 SGBD 和 MySQL:
# 需要一个 mysql 或 pgres 参数
import os
import sys
# 正在使用 MySQL 配置应用程序
import config
config = config.configure({'sgbd': "mysql"})
# 依赖项
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
…
第7行,将SGBD设置为MySQL。
- [main_withpgres] 文件由 [2] 生成,其对应文件为 [main](由 [1] 生成),并进行了以下修改: 它使用了 SGBD 和 PostgreSQL:
# 需要指定 mysql 或 pgres 参数
import os
import sys
# 正在使用 MySQL 配置应用程序
import config
config = config.configure({'sgbd': "pgres"})
# 依赖项
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
…
第 7 行,将 SGBD 修改为 PostgreSQL。
完成上述操作后,创建如下脚本 [main_withmysql.wsgi](后缀不重要):
脚本 [main_withmysql.wsgi] 将作为目标,由 Apache 服务器在 WSGI 模式下执行:
- Apache服务器的目标本可以是脚本[main_withmysql.py],就像之前对脚本[date_time_server.py]所做的那样。但需要对其进行一些修改:
- 与使用控制台脚本的执行模式不同,在 Apache 环境中,包含目标 [main_withmysql.py] 的文件夹并不属于 Python Path。因此,脚本 [main_withmysql.py] 的第 6 行会引发错误;
- 需要进行的第二项修改是:在 [main_withmysql] 中,Flask 应用程序应通过标识符 [app] 进行引用。 已知对于 Apache / WSGI,它也必须通过标识符 [application] 进行引用;
- 与其修改 [main_withmysql.py],不如更改 Apache 的目标。今后将使用上述的 [main_withmysql.wsgi] 脚本:
- 第1-7行:将脚本所在的文件夹添加到Python路径中。因此,[main_withmysql.py]中的第6行不再引发错误;
- 第 9-10 行:导入 [main_withmysql.py] 会触发其执行。 此外,通过标识符 [application] 引用 [main_withmysql.py] 中找到的 Flask 应用程序 [app],该标识符是 Apache 在 WSGI 模式下所需的;
对脚本 [main_withpgres.wsgi] 也采用同样的处理方式:
# 该文件的目录
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
# 将其添加到 syspath 中,以便后续能够进行导入
import sys
sys.path.insert(0, script_dir)
# 导入 Flask 应用程序 [app],并将其命名为 [application]
from main_withpgres import app as application
现在我们已经有了Apache服务器的可执行目标。接下来需要创建两个虚拟服务器,每个目标对应一个。
在 [<laragon>\etc\apache2\sites-enabled] 中,我们创建文件 [flask-impots-withmysql.conf](文件名不限):

# .wsgi 脚本文件夹
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
# 由该文件配置的网站名称
# 此处将命名为 flask-impots-withmysql
# URL 将采用 http(s)://flask-impots-withmysql/路径 的形式
define SITE "flask-impots-withmysql"
# 将 IP 的地址 127.0.0.1 添加到 c:/windows/system32/drivers/etc/hosts 文件中,用于 SITE 网站
# 在此处填写要使用的 Python 库路径——用逗号分隔
# 此处填写 Python 虚拟环境的库
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# Python 安装目录 - 仅当安装了多个 Python 版本时才需要
# WSGIPythonHome "C:/Program Files/Python38"
# URL HTTP
<VirtualHost *:80>
# 使用别名 /,URL 将呈现为 /{prefixe_url}/action/...
# 使用别名 /impots 时,URL 将采用 /impots/{prefixe_url}/action/... 的形式
# 其中 [prefixe_url] 在 parameters.py 中定义
WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL 通过 HTTPS 进行安全保护
<VirtualHost *:443>
# 使用别名 /,URL 将采用 /{prefixe_url}/action/... 的形式
# 别名为 /impots,URL 的格式为 /impots/{prefixe_url}/action/...
# 其中 [prefixe_url] 在 parameters.py 中定义
WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/myprograms/laragon/etc/ssl/laragon.key
</VirtualHost>
- 第 2 行:应用程序根目录,即我们创建的 [apache] 文件夹;
- 第 23、38 行:我们创建的目标 [main_witmysql.wsgi]:
- 第 7 行:虚拟服务器将命名为 [flask-impots-withmysql];
- 第 13 行:[WSGIPythonPath] 指令用于将文件夹添加到 Python 路径中。在此,Apache 并不知道我们使用了虚拟环境来开发应用程序,也不知道应用程序使用的所有模块都位于该虚拟环境中。因此,在第 13 行,我们添加了包含所用虚拟环境中所有模块的文件夹。 一种方法是将该文件夹复制到文件系统的其他位置,并引用该位置。另一种方法是直接在目标 [main_witmysql.wsgi] 中将该文件夹添加到 Python Path 中(这可能是更好的解决方案);
- 第 16 行:可向 Apache 指定文件系统中 Python 的安装目录。 通常,该路径位于机器的 PATH 中,因此此行通常是多余的(本例中即如此)。但机器上可能存在多个 Python 安装,且所需的 Python 版本未包含在机器的 PATH 中。此时,此行代码即可解决问题;
同样地,创建一个名为 [flask-impots-withpgres.conf] 的文件:
# .wsgi脚本文件夹
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
# 由该文件配置的网站名称
# 此处将命名为 flask-impots-withmysql
# URL 将采用 http(s)://flask-impots-withmysql/路径 的形式
define SITE "flask-impots-withpgres"
# 将 IP 的地址 127.0.0.1 添加到 c:/windows/system32/drivers/etc/hosts 文件中,用于 SITE 网站
# 在此处填写要使用的 Python 库路径——用逗号分隔
# 此处填写 Python 虚拟环境的库
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# Python Home - 仅当安装了多个 Python 版本时才需要
# WSGIPythonHome "C:/Program Files/Python38"
# URL HTTP
<VirtualHost *:80>
# 使用别名 /,URL 将呈现为 /{prefixe_url}/action/...
# 使用别名 /impots 时,URL 将采用 /impots/{prefixe_url}/action/... 的形式
# 其中 [prefixe_url] 在 parameters.py 中定义
WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL 通过 HTTPS 进行安全保护
<VirtualHost *:443>
# 使用别名 /,URL 将采用 /{prefixe_url}/action/... 的形式
# 别名为 /impots,URL 的格式为 /impots/{prefixe_url}/action/...
# 其中 [prefixe_url] 在 parameters.py 中定义
WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile C:/MyPrograms/laragon/etc/ssl/laragon.crt
SSLCertificateKeyFile C:/myprograms/laragon/etc/ssl/laragon.key
</VirtualHost>
保存所有这些文件后,启动 Apache 服务器以及 SGBD、MySQL 和 PostgreSQL。 应用程序配置为使用 URL、[/do] 和 [with_csrftoken=False] (无令牌 CSRF)配置在 [configs/parameters.py] 中。我们请求 URL [https://flask-impots-withmysql/do]。服务器的响应如下:

现在我们请求 URL 和 [https://flask-impots-pgres/do]。响应如下:

这两个应用程序均运行正常。
现在将参数 [WSGIScriptAlias] 修改为 [flask-impots-withmysql.conf]:
# .wsgi脚本文件夹
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
…
# URL HTTP
<VirtualHost *:80>
# 使用别名 / 时,URL 将呈现为 /{prefixe_url}/action/...
# 使用别名 /impots 时,URL 将采用 /impots/{prefixe_url}/action/... 的形式
# 其中 [prefixe_url] 在 parameters.py 中定义
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
# URL 通过 HTTPS 进行安全保护
<VirtualHost *:443>
# 的别名为 /,而 URL 的格式为 /{prefixe_url}/action/...
# 别名为 /impots,URL 的形式将为 /impots/{prefixe_url}/action/...
# 其中 [prefixe_url] 在 parameters.py 中定义
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
- 第11行和第20行,别名 WSI 现改为 [/impots];
我们停止/重启 Apache 服务器,然后请求 URL [https://flask-impots-withmysql/impots/do]。服务器的响应如下:

系统发生崩溃。URL [1] 指出了原因。它本应是 [https://flask-impots-withmysql/impots/do/afficher-vue-authentification]。 缺少别名 WSGI。这是我们应用程序的错误。它能够处理 URL 这种前缀(/do 确实存在)。 人们可能会认为,给应用程序添加前缀 [/impots/do] 就能解决上述问题。但事实并非如此。这样反而会引发其他类型的问题。别名 WSGI 并未表现为 URL 的前缀。
让我们试着弄清楚发生了什么。我们请求了 URL 和 [https://flask-impots-withmysql/impots/do]。 我们原本期望看到身份验证页面。在 [1] 中,上文可见应用程序请求了该页面的显示,但使用的并非正确的 URL。让我们来分析 [https://flask-impots-withmysql/impots/do] 请求的处理路径。
首先执行了以下路由(configs/routes.py):
# Flask 应用程序的路由
# 应用程序根目录
app.add_url_rule(f'{prefix_url}/', methods=['GET'],
view_func=routes.index)
在本例中,第 3 行中的路由为 [https://flask-impots-withmysql/impots/do]。 可以看到,该路由去除了 [https://flask-impots-withmysql/impots] 部分,简化为 [/do]。对于 [https://flask-impots-withmysql] 部分,这是正常的,因为服务器名称并未包含在路由中。 但可以看到,它也没有包含别名 WSGI [/impots]。这一点很重要。即使存在别名 WSGI,我们的初始路由仍然有效。
现在我们来看看第 4 行(configs/[index])中的 [index] 函数的作用:
# 应用程序根目录
def index() -> tuple:
# 重定向至 /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
第 4 行,我们被重定向到 [init_session] 函数中的 URL。在 [configs/routes.py] 中,该函数已被关联到路径 [/do/init-session/html]:
# init-session
app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
view_func=routes.init_session)
第2行,在我们的测试中,[csrftoken_param]是空字符串。该应用程序在此处不支持CSRF令牌。
函数 [init_session] 的定义如下(configs/routes_without_csrftoken):
# init-session
def init_session(type_response: str) -> tuple:
# 执行与该操作关联的控制器
return front_controller()
第 4 行,开始处理 [init-session] 操作的字符串。该字符串在 [responses/HtmlResponse] 中以如下方式结束:
…
# 现在需要生成重定向的URL,如果需要,别忘了CSRF令牌
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# 重定向响应
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
操作 [init-session] 是一个 ADS 操作(Do Something 操作),该操作以重定向到视图结束(第 9 行)。问题就出在这里。 第9行的[redirect]函数并未自动将别名WSGI添加到重定向对象URL中。 上图的屏幕截图展示了这一点。重定向对象 URL 中缺少别名 /impots。
下一个版本将解决 WSGI 别名的问题。


